import { OpenIDConnectToken } from "../models/OpenIDConnect" ; import { doHttpRequest } from "../http" ; import { timedIdentityClientFunctionCall } from "../metrics/decorators" ; import { Policies , TranslatedPolicy } from "../models/Policies" ; import { Metrics } from "../metrics/Metrics" ; import { Threepid } from "../models/Threepid" ; import * as crypto from "crypto" ; import { UnpaddedBase64 } from "../helpers/UnpaddedBase64" ; import { MatrixClient } from "../MatrixClient" ; import { MatrixProfile , MatrixProfileInfo } from "../models/MatrixProfile" ; import { IdentityServerAccount , IdentityServerInvite } from "../models/IdentityServerModels" ; category export class IdentityClient { /**
     * The metrics instance for this client. Note that metrics for the underlying MatrixClient will
     * not be available here.
     */ public readonly metrics : Metrics ; /**
     * If truthy, this is a string that will be supplied as `?brand=$brand` where endpoints can
     * result in communications to a user.
     */ public brand : string ; private constructor ( public readonly accessToken , public readonly serverUrl , public readonly matrixClient ) { this . metrics = new Metrics ( ) ; } returns { Promise < IdentityServerAccount > } @ timedIdentityClientFunctionCall ( ) public getAccount ( ) { return this . doRequest ( "GET" , "/_matrix/identity/v2/account" ) ; } returns { Promise < Policies > } @ timedIdentityClientFunctionCall ( ) public getTermsOfService ( ) { return this . doRequest ( "GET" , "/_matrix/identity/v2/terms" ) ; } param { string [ ] } termsUrls returns { Promise < void > } @ timedIdentityClientFunctionCall ( ) public acceptTerms ( termsUrls ) { return this . doRequest ( "POST" , "/_matrix/identity/v2/terms" , null , { user_accepts : termsUrls , } ) ; } returns { Promise < void > } @ timedIdentityClientFunctionCall ( ) public async acceptAllTerms ( ) { const terms = await this . getTermsOfService ( ) ; const urls = new Set < string > ( ) ; for ( const policy of Object . values ( terms . policies ) ) { let chosenLang = policy [ "en" ] as TranslatedPolicy ; if ( ! chosenLang ) { chosenLang = policy [ Object . keys ( policy ) . find ( k => k !== "version" ) ] as TranslatedPolicy ; } if ( ! chosenLang ) continue ; urls . add ( chosenLang . url ) ; } return await this . acceptTerms ( Array . from ( urls ) ) ; } param { Threepid [ ] } identifiers param { boolean } allowPlaintext returns { Promise < string [ ] > } @ timedIdentityClientFunctionCall ( ) public async lookup ( identifiers , allowPlaintext = false ) { const hashInfo = await this . doRequest ( "GET" , "/_matrix/identity/v2/hash_details" ) ; if ( ! hashInfo ?. [ "algorithms" ] ) throw new Error ( "Server not supported: invalid response" ) ; const algorithms = hashInfo ?. [ "algorithms" ] ; let algorithm = algorithms . find ( a => a === "sha256" ) ; if ( ! algorithm && allowPlaintext ) algorithm = algorithms . find ( a => a === "none" ) ; if ( ! algorithm ) throw new Error ( "No supported hashing algorithm found" ) ; const body = { algorithm , pepper : hashInfo [ "lookup_pepper" ] , addresses : [ ] , } ; for ( const pid of identifiers ) { let transformed = null ; switch ( algorithm ) { case "none" : transformed = `${ pid . address . toLowerCase ( ) } ${ pid . kind }` ; break ; case "sha256" : transformed = UnpaddedBase64 . encodeBufferUrlSafe ( crypto . createHash ( "sha256" ) . update ( `${ pid . address . toLowerCase ( ) } ${ pid . kind } ${ body . pepper }` ) . digest ( ) ) ; break ; default : throw new Error ( "Unsupported hashing algorithm (programming error)" ) ; } body . addresses . push ( transformed ) ; } const resp = await this . doRequest ( "POST" , "/_matrix/identity/v2/lookup" , null , body ) ; const mappings = resp ?. [ "mappings" ] || { } ; const mxids = [ ] ; for ( const addr of body . addresses ) { mxids . push ( mappings [ addr ] ) ; } return mxids ; } param { string } emailAddress param { string } roomId returns { Promise < IdentityServerInvite > } @ timedIdentityClientFunctionCall ( ) public async makeEmailInvite ( emailAddress , roomId ) { const req = { address : emailAddress , medium : "email" , room_id : roomId , sender : await this . matrixClient . getUserId ( ) , } ; const tryFetch = async ( eventType , stateKey ) : Promise < any > => { try { return await this . matrixClient . getRoomStateEvent ( roomId , eventType , stateKey ) ; } catch ( e ) { return null ; } } ; const canonicalAlias = ( await tryFetch ( "m.room.canonical_alias" , "" ) ) ?. [ "alias" ] ; const roomName = ( await tryFetch ( "m.room.name" , "" ) ) ?. [ "name" ] ; req [ "room_alias" ] = canonicalAlias ; req [ "room_avatar_url" ] = ( await tryFetch ( "m.room.avatar" , "" ) ) ?. [ "url" ] ; req [ "room_name" ] = roomName || canonicalAlias ; req [ "room_join_rules" ] = ( await tryFetch ( "m.room.join_rules" , "" ) ) ?. [ "join_rule" ] ; let profileInfo ; try { profileInfo = await this . matrixClient . getUserProfile ( await this . matrixClient . getUserId ( ) ) ; } catch ( e ) { } const senderProfile = new MatrixProfile ( await this . matrixClient . getUserId ( ) , profileInfo ) ; req [ "sender_avatar_url" ] = senderProfile . avatarUrl ; req [ "sender_display_name" ] = senderProfile . displayName ; const inviteReq = { } ; for ( const entry of Object . entries ( req ) ) { if ( ! ! entry [ 1 ] ) inviteReq [ entry [ 0 ] ] = entry [ 1 ] ; } const qs = { } ; if ( this . brand ) qs [ 'brand' ] = this . brand ; return await this . doRequest ( "POST" , "/_matrix/identity/v2/store-invite" , qs , inviteReq ) ; } param { "GET" | "POST" | "PUT" | "DELETE" } method param { string } endpoint param { any } qs param { any } body param { number } timeout param { boolean } raw param { string } contentType param { string } noEncoding returns { Promise < any > } @ timedIdentityClientFunctionCall ( ) public doRequest ( method , endpoint , qs = null , body = null , timeout = 60000 , raw = false , contentType = "application/json" , noEncoding = false ) { const headers = { } ; if ( this . accessToken ) { headers [ "Authorization" ] = `Bearer ${ this . accessToken }` ; } return doHttpRequest ( this . serverUrl , method , endpoint , qs , body , headers , timeout , raw , contentType , noEncoding ) ; } param { OpenIDConnectToken } oidc param { string } serverUrl public static async acquire ( oidc , serverUrl , mxClient ) { const account = await doHttpRequest ( serverUrl , "POST" , "/_matrix/identity/v2/account/register" , null , oidc ) ; return new IdentityClient ( account [ 'token' ] , serverUrl , mxClient ) ; } }