import { PBKDF2Params , HMACParams , HMACKey } from "./crypto" ; import { getCryptoProvider as getProvider } from "./platform" ; import { SimpleContainer } from "./container" ; import { stringToBytes , bytesToString , bytesToHex , marshal , unmarshal , concatBytes , Serializable , AsSerializable , AsDate , AsBytes } from "./encoding" ; import { Account , AccountID } from "./account" ; import { Org , OrgID } from "./org" ; import { uuid } from "./util" ; import { Err , ErrorCode } from "./error" ; export type InvitePurpose = "join_org" | "confirm_membership" ; /**
 * Unique identifier for [[Invite]]s.
 */ export type InviteID = string ; class OrgInfo extends Serializable { id : OrgID = "" ; name : string = "" ; @ AsBytes ( ) publicKey ! : Uint8Array ; /**
     * Signature created using the HMAC key derived from [[secret]]
     * Used by invitee to verify organization details.
     */ @ AsBytes ( ) signature ! : Uint8Array ; constructor ( vals ) { super ( ) ; Object . assign ( this , vals ) ; } } class InviteeInfo extends Serializable { id : AccountID = "" ; name : string = "" ; email : string = "" ; @ AsBytes ( ) publicKey ! : Uint8Array ; /**
     * Signature created using the HMAC key derived from [[Invite.secret]]
     * Used by organization owner to verify invitee details.
     */ @ AsBytes ( ) signature ! : Uint8Array ; /**
     * Signature of organization details created using the invitee accounts
     * own secret signing key. Will be stored on the [[Member]] object to
     * allow the member to verify the organization details at a later time.
     */ @ AsBytes ( ) orgSignature ! : Uint8Array ; constructor ( vals ) { super ( ) ; Object . assign ( this , vals ) ; } } /**
 * The `Invite` class encapsules most of the logic and information necessary to
 * perform a key exchange between an [[Org]] and [[Account]] before adding the
 * [[Account]] as a member. A secret HMAC key is used to sign and verify the public keys
 * of both invitee and organization. This key is derived from a [[secret]], which
 * needs to be communicated between the organization owner and invitee directly.
 *
 * The invite flow generally works as follows:
 *
 * ```ts
 * // ORG OWNER
 *
 * const invite = new Invite("bob@example.com", "add_member");
 *
 * // Generates random secret and signs organization details
 * await invite.intialize(org, orgOwnerAccount);
 *
 * console.log("invite secret: ", invite.secret);
 *
 * // => Invite object is send to server, which sends an email to the invitee
 *
 * // INVITEE
 * // => Invitee fetches `invite` object from server, asks org owner for `secret` (in person)
 *
 * // Verifies organization info and signs own public key
 *
 * const success = await invite.accept(inviteeAccount, secret);
 *
 * if (!success) {
 *     throw "Verification failed! Incorrect secret?";
 * }
 *
 * // => Sends updated invite object to server
 *
 * // ORG OWNER
 *
 * // => Fetches updated invite object
 *
 * // Verify invitee details.
 * if (!(await invite.verifyInvitee())) {
 *     throw "Failed to verify invitee details!";
 * }
 *
 * // DONE!
 * await org.addOrUpdateMember(invite.invitee);
 * ```
 */ export class Invite extends SimpleContainer { /** Unique identfier */ id : InviteID = "" ; /** Time of creation */ @ AsDate ( ) created = new Date ( ) ; /**
     * Expiration time used to limit invite procedure to a certain time
     * window. This property is also stored in [[encryptedData]] along
     * with the invite secret to prevent tempering.
     */ @ AsDate ( ) expires = new Date ( ) ; /**
     * Organization info, including HMAC signature used for verification.
     * Set during initialization
     */ @ AsSerializable ( OrgInfo ) org ! : OrgInfo ; /**
     * Invitee info, including HMAC signature used for verification
     * Set when the invitee successfully accepts the invite
     */ @ AsSerializable ( InviteeInfo ) invitee ! : InviteeInfo ; /** Info about who created the invite. */ invitedBy ? : { id : AccountID ; name : string ; email : string ; } = undefined ; secret set secret ( s ) { this . _secret = s ; this . _signingKey = null ; } get secret ( ) { return this . _secret ; } /** Whether this invite has expired */ get expired ( ) : boolean { return new Date ( ) > new Date ( this . expires ) ; } /** Whether this invite has been accepted by the invitee */ get accepted ( ) : boolean { return ! ! this . invitee ; } private _secret : string = "" ; private _signingKey : HMACKey | null = null ; /** Key derivation paramaters used for deriving the HMAC signing key from [[secret]]. */ @ AsSerializable ( PBKDF2Params ) signingKeyParams = new PBKDF2Params ( { iterations : 1e6 } ) ; /**
     * Parameters used for signing organization and initee details.
     */ @ AsSerializable ( HMACParams ) signingParams = new HMACParams ( ) ; constructor ( /** invitee email */ public email = "" , /** purpose of the invite */ public purpose = "join_org" ) { super ( ) ; } param org param invitor param duration async initialize ( org , invitor , duration = 12 ) { this . id = await uuid ( ) ; this . invitedBy = { id : invitor . id , email : invitor . email , name : invitor . name } ; this . secret = bytesToHex ( await getProvider ( ) . randomBytes ( 4 ) ) ; this . expires = new Date ( Date . now ( ) + 1000 * 60 * 60 * duration ) ; this . _key = org . invitesKey ; await this . setData ( stringToBytes ( marshal ( { secret : this . secret , expires : this . expires } ) ) ) ; this . signingKeyParams . salt = await getProvider ( ) . randomBytes ( 16 ) ; this . org = new OrgInfo ( { id : org . id , name : org . name , publicKey : org . publicKey , signature : await this . _sign ( concatBytes ( [ stringToBytes ( org . id ) , org . publicKey ] , 0x00 ) ) } ) ; } /**
     * "Unlocks" the invite with the dedicated key (owned by the respective [[Org]]).
     * This grants access to the [[secret]] property and verfies that [[expires]] has
     * not been tempered with.
     */ async unlock ( key ) { await super . unlock ( key ) ; const { secret , expires } = unmarshal ( bytesToString ( await this . getData ( ) ) ) ; this . secret = secret ; if ( this . expires . getTime ( ) !== new Date ( expires ) . getTime ( ) ) { throw new Err ( ErrorCode . VERIFICATION_ERROR ) ; } } lock ( ) { super . lock ( ) ; delete this . secret ; delete this . _signingKey ; } /**
     * Accepts the invite by verifying the organization details and, if successful,
     * signing and storing the invitees own information. Throws if verification
     * is unsuccessful.
     */ async accept ( account , secret ) { this . secret = secret ; if ( ! ( await this . verifyOrg ( ) ) ) { this . secret = "" ; return false ; } this . invitee = new InviteeInfo ( { id : account . id , name : account . name , email : account . email , publicKey : account . publicKey , signature : await this . _sign ( concatBytes ( [ stringToBytes ( account . id ) , stringToBytes ( account . email ) , account . publicKey ] , 0x00 ) ) , orgSignature : await account . signOrg ( this . org ) } ) ; return true ; } /** Verifies the organization information. */ async verifyOrg ( ) { if ( ! this . org ) { throw "Invite needs to be initialized first!" ; } return ( this . expires > new Date ( ) && this . _verify ( this . org . signature , concatBytes ( [ stringToBytes ( this . org . id ) , this . org . publicKey ] , 0x00 ) ) ) ; } /** Verifies the invitee information. */ async verifyInvitee ( ) { if ( ! this . invitee ) { throw "Invite needs to be accepted first!" ; } return ( this . expires > new Date ( ) && this . _verify ( this . invitee . signature , concatBytes ( [ stringToBytes ( this . invitee . id ) , stringToBytes ( this . invitee . email ) , this . invitee . publicKey ] , 0x00 ) ) ) ; } private async _getSigningKey ( ) { if ( ! this . _signingKey ) { this . _signingKey = ( await getProvider ( ) . deriveKey ( stringToBytes ( this . secret ) , this . signingKeyParams ) ) as HMACKey ; } return this . _signingKey ; } private async _sign ( val ) { return getProvider ( ) . sign ( await this . _getSigningKey ( ) , val , this . signingParams ) ; } private async _verify ( sig , val ) { return await getProvider ( ) . verify ( await this . _getSigningKey ( ) , sig , val , this . signingParams ) ; } }