import URL from "core-js-pure/features/url" ; import URLSearchParams from "core-js-pure/features/url-search-params" ; import { ContainerStorage , JSONObject , JSONValue , User , Identity , AuthResponse , SSOLoginOptions , ContainerOptions , Session , ExtraSessionInfoOptions , OAuthError , } from "./types" ; import { BaseAPIClient } from "./client" ; const defaultExtraSessionInfoOptions = { deviceName : undefined } ; public export class AuthContainer < T extends BaseAPIClient > { parent : Container < T > ; /**
     * Current logged in user.
     */ currentUser : User | null ; /**
     * Session ID of current logged in user.
     */ currentSessionID : string | null ; /**
     * Extra session information to be submitted to server.
     */ extraSessionInfoOptions : ExtraSessionInfoOptions = { ... defaultExtraSessionInfoOptions } ; constructor ( parent ) { this . parent = parent ; this . currentUser = null ; this . currentSessionID = null ; } remarks async saveExtraSessionInfoOptions ( ) { return this . parent . storage . setExtraSessionInfoOptions ( this . parent . name , this . extraSessionInfoOptions ) ; } internal async _getExtraSessionInfo ( ) { const options = this . extraSessionInfoOptions ; let hasInfo = false ; const info = { } ; if ( options . deviceName ) { info [ "device_name" ] = options . deviceName ; hasInfo = true ; } return hasInfo ? info : null ; } internal async persistAuthResponse ( response ) { const { user , accessToken , refreshToken , sessionID , expiresIn } = response ; await this . parent . storage . setUser ( this . parent . name , user ) ; if ( accessToken ) { await this . parent . storage . setAccessToken ( this . parent . name , accessToken ) ; } if ( refreshToken ) { await this . parent . storage . setRefreshToken ( this . parent . name , refreshToken ) ; } if ( sessionID ) { await this . parent . storage . setSessionID ( this . parent . name , sessionID ) ; } this . currentUser = user ; if ( accessToken ) { this . parent . apiClient . setAccessTokenAndExpiresIn ( accessToken , expiresIn ) ; } if ( sessionID ) { this . currentSessionID = sessionID ; } } internal async handleAuthResponse ( p ) { const response = await p ; await this . persistAuthResponse ( response ) ; return response . user ; } internal async handleMaybeAuthResponse ( p ) { const response = await p ; if ( ! response ) { return null ; } await this . persistAuthResponse ( response ) ; return response . user ; } example param loginIDs param password param options async signup ( loginIDs , password , options ? ) { return this . handleAuthResponse ( this . parent . apiClient . signup ( loginIDs , password , options ) ) ; } remarks Equivalent to  AuthContainer . signup : `signup({"email": email}, password, options)`.
      async signupWithEmail ( email , password , options ? ) { return this . signup ( { email } , password , options ) ; } remarks Equivalent to  AuthContainer . signup : `signup({"username": username}, password, options)`.
      async signupWithUsername ( username , password , options ? ) { return this . signup ( { username } , password , options ) ; } example param loginID param password param options async login ( loginID , password , options ? ) { return this . handleAuthResponse ( this . parent . apiClient . login ( loginID , password , options ) ) ; } remarks param options async logout ( options = { } ) { const { force = false } = options ; try { await this . parent . apiClient . logout ( ) ; } catch ( err ) { if ( ! force ) { throw err ; } } await this . _clearSession ( ) ; } internal async _clearSession ( ) { await this . parent . storage . delUser ( this . parent . name ) ; await this . parent . storage . delAccessToken ( this . parent . name ) ; await this . parent . storage . delRefreshToken ( this . parent . name ) ; await this . parent . storage . delSessionID ( this . parent . name ) ; this . currentUser = null ; this . parent . apiClient . _accessToken = null ; this . currentSessionID = null ; } internal async _refreshAccessToken ( ) { const refreshToken = await this . parent . storage . getRefreshToken ( this . parent . name ) ; if ( ! refreshToken ) { this . parent . apiClient . setShouldNotRefreshToken ( ) ; return false ; } let tokenResponse ; try { tokenResponse = await this . parent . apiClient . _oidcTokenRequest ( { grant_type : "refresh_token" , client_id : this . parent . apiClient . apiKey , refresh_token : refreshToken } ) ; } catch ( error ) { if ( error . error === "invalid_grant" ) { await this . _clearSession ( ) ; return false ; } throw error ; } await this . parent . storage . setAccessToken ( this . parent . name , tokenResponse . access_token ) ; if ( tokenResponse . refresh_token ) { await this . parent . storage . setRefreshToken ( this . parent . name , tokenResponse . refresh_token ) ; } this . parent . apiClient . setAccessTokenAndExpiresIn ( tokenResponse . access_token , tokenResponse . expires_in ) ; return true ; } /**
     * Refreshes and returns current user information.
     */ async me ( ) { return this . handleAuthResponse ( this . parent . apiClient . me ( ) ) ; } param newPassword param oldPassword async changePassword ( newPassword , oldPassword ) { return this . handleAuthResponse ( this . parent . apiClient . changePassword ( newPassword , oldPassword ) ) ; } remarks param metadata async updateMetadata ( metadata ) { return this . handleAuthResponse ( this . parent . apiClient . updateMetadata ( metadata ) ) ; } param email async requestForgotPasswordEmail ( email ) { return this . parent . apiClient . requestForgotPasswordEmail ( email ) ; } param form async resetPassword ( form ) { return this . parent . apiClient . resetPassword ( form ) ; } param email async requestEmailVerification ( email ) { return this . parent . apiClient . requestEmailVerification ( email ) ; } param phone async requestPhoneVerification ( phone ) { return this . parent . apiClient . requestPhoneVerification ( phone ) ; } param code async verifyWithCode ( code ) { return this . parent . apiClient . verifyWithCode ( code ) ; } param token param options async loginWithCustomToken ( token , options ? ) { return this . handleAuthResponse ( this . parent . apiClient . loginWithCustomToken ( token , options ) ) ; } param providerID async deleteOAuthProvider ( providerID ) { return this . parent . apiClient . deleteOAuthProvider ( providerID ) ; } remarks param providerID param accessToken param options async loginOAuthProviderWithAccessToken ( providerID , accessToken , options ? ) { return this . handleAuthResponse ( this . parent . apiClient . loginOAuthProviderWithAccessToken ( providerID , accessToken , options ) ) ; } remarks param providerID param accessToken async linkOAuthProviderWithAccessToken ( providerID , accessToken ) { return this . handleAuthResponse ( this . parent . apiClient . linkOAuthProviderWithAccessToken ( providerID , accessToken ) ) ; } /**
     * Lists all active user sessions.
     */ async listSessions ( ) { return this . parent . apiClient . listSessions ( ) ; } /**
     * Gets information on specified active user session.
     */ async getSession ( id ) { return this . parent . apiClient . getSession ( id ) ; } param id async revokeSession ( id ) { return this . parent . apiClient . revokeSession ( id ) ; } /**
     * Revokes all other active user sessions.
     */ async revokeOtherSessions ( ) { return this . parent . apiClient . revokeOtherSessions ( ) ; } /**
     * Lists all identities of current user.
     */ async listIdentities ( ) { return this . parent . apiClient . listIdentities ( ) ; } /**
     * Adds new login ID to current user.
     */ async addLoginID ( loginID ) { return this . parent . apiClient . addLoginID ( loginID ) ; } /**
     * Removes login ID from current user.
     */ async removeLoginID ( loginID ) { return this . parent . apiClient . removeLoginID ( loginID ) ; } /**
     * Updates specified login ID for current user.
     */ async updateLoginID ( oldLoginID , newLoginID ) { return this . handleAuthResponse ( this . parent . apiClient . updateLoginID ( oldLoginID , newLoginID ) ) ; } } public export interface AuthorizeOptions { /**
     * Redirect uri. Redirection URI to which the response will be sent after authorization.
     */ redirectURI : string ; /**
     * OAuth 2.0 state value.
     */ state ? : string ; /**
     * OIDC prompt parameter.
     */ prompt ? : string ; /**
     * OIDC login hint parameter
     */ loginHint ? : string ; /**
     * UI locale tags
     */ uiLocales ? : string [ ] ; } public export interface PromoteOptions { /**
     * Redirect uri. Redirection URI to which the response will be sent after authorization.
     */ redirectURI : string ; /**
     * OAuth 2.0 state value.
     */ state ? : string ; /**
     * UI locale tags
     */ uiLocales ? : string [ ] ; } public export abstract class OIDCContainer < T extends BaseAPIClient > { parent : Container < T > ; auth : AuthContainer < T > ; abstract clientID : string ; abstract isThirdParty : boolean ; abstract async _setupCodeVerifier ( ) ; constructor ( parent , auth ) { this . parent = parent ; this . auth = auth ; } async authorizeEndpoint ( options ) { const config = await this . parent . apiClient . _fetchOIDCConfiguration ( ) ; const query = new URLSearchParams ( ) ; if ( this . isThirdParty ) { const codeVerifier = await this . _setupCodeVerifier ( ) ; await this . parent . storage . setOIDCCodeVerifier ( this . parent . name , codeVerifier . verifier ) ; query . append ( "response_type" , "code" ) ; query . append ( "scope" , "openid offline_access https://auth.skygear.io/scopes/full-access" ) ; query . append ( "code_challenge_method" , "S256" ) ; query . append ( "code_challenge" , codeVerifier . challenge ) ; } else { query . append ( "response_type" , "none" ) ; query . append ( "scope" , "openid https://auth.skygear.io/scopes/full-access" ) ; } query . append ( "client_id" , this . clientID ) ; query . append ( "redirect_uri" , options . redirectURI ) ; if ( options . state ) { query . append ( "state" , options . state ) ; } if ( options . prompt ) { query . append ( "prompt" , options . prompt ) ; } if ( options . loginHint ) { query . append ( "login_hint" , options . loginHint ) ; } if ( options . uiLocales ) { query . append ( "ui_locales" , options . uiLocales . join ( " " ) ) ; } return `${ config . authorization_endpoint }?${ query . toString ( ) }` ; } async _finishAuthorization ( url ) { const u = new URL ( url ) ; const params = u . searchParams ; const uu = new URL ( url ) ; uu . hash = "" ; uu . search = "" ; const redirectURI = uu . toString ( ) ; if ( params . get ( "error" ) ) { const err = { error : params . get ( "error" ) , error_description : params . get ( "error_description" ) } as OAuthError ; throw err ; } let authResponse ; let tokenResponse ; if ( ! this . isThirdParty ) { authResponse = await this . parent . apiClient . _oidcUserInfoRequest ( ) ; } else { const code = params . get ( "code" ) ; if ( ! code ) { const missingCodeError = { error : "invalid_request" , error_description : "Missing parameter: code" } as OAuthError ; throw missingCodeError ; } const codeVerifier = await this . parent . storage . getOIDCCodeVerifier ( this . parent . name ) ; tokenResponse = await this . parent . apiClient . _oidcTokenRequest ( { grant_type : "authorization_code" , code : code , redirect_uri : redirectURI , client_id : this . clientID , code_verifier : codeVerifier || "" } ) ; authResponse = await this . parent . apiClient . _oidcUserInfoRequest ( tokenResponse . access_token ) ; } const ar = { ... authResponse } ; if ( tokenResponse ) { ar . accessToken = tokenResponse . access_token ; ar . refreshToken = tokenResponse . refresh_token ; ar . expiresIn = tokenResponse . expires_in ; } await this . auth . persistAuthResponse ( ar ) ; return { user : authResponse . user , state : params . get ( "state" ) || undefined } ; } remarks param options async _logout ( options = { } ) { if ( this . isThirdParty ) { try { const refreshToken = ( await this . parent . storage . getRefreshToken ( this . parent . name ) ) || "" ; await this . parent . apiClient . _oidcRevocationRequest ( refreshToken ) ; } catch ( error ) { if ( ! options . force ) { throw error ; } } await this . auth . _clearSession ( ) ; } else { const config = await this . parent . apiClient . _fetchOIDCConfiguration ( ) ; const query = new URLSearchParams ( ) ; if ( options . redirectURI ) { query . append ( "post_logout_redirect_uri" , options . redirectURI ) ; } const endSessionEndpoint = `${ config . end_session_endpoint }?${ query . toString ( ) }` ; await this . auth . _clearSession ( ) ; window . location . href = endSessionEndpoint ; } } } remarks public export class Container < T extends BaseAPIClient > { defaultValue name : string ; apiClient : T ; storage : ContainerStorage ; classicAuth : AuthContainer < T > ; constructor ( options ) { if ( ! options . apiClient ) { throw Error ( "missing apiClient" ) ; } if ( ! options . storage ) { throw Error ( "missing storage" ) ; } this . name = options . name || "default" ; this . apiClient = options . apiClient ; this . storage = options . storage ; this . classicAuth = new AuthContainer ( this ) ; } protected async _configure ( options ) { this . apiClient . apiKey = options . apiKey ; await this . apiClient . setEndpoint ( options . endpoint , options . authEndpoint , options . assetEndpoint ) ; const accessToken = await this . storage . getAccessToken ( this . name ) ; this . apiClient . _accessToken = accessToken ; this . apiClient . setShouldRefreshTokenNow ( ) ; const user = await this . storage . getUser ( this . name ) ; this . classicAuth . currentUser = user ; const sessionID = await this . storage . getSessionID ( this . name ) ; this . classicAuth . currentSessionID = sessionID ; const extraSessionInfoOptions = await this . storage . getExtraSessionInfoOptions ( this . name ) ; this . classicAuth . extraSessionInfoOptions = { ... defaultExtraSessionInfoOptions , ... extraSessionInfoOptions } ; this . apiClient . refreshTokenFunction = this . classicAuth . _refreshAccessToken . bind ( this . classicAuth ) ; this . apiClient . getExtraSessionInfo = this . classicAuth . _getExtraSessionInfo . bind ( this . classicAuth ) ; } remarks async fetch ( input , init ? ) { return this . apiClient . fetch ( this . apiClient . appEndpoint , input , init ) ; } }