diff --git a/CHANGELOG.md b/CHANGELOG.md index 2065e49..024111c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.9] - 2024-06-20 +### Fix +- Fix Shield authentication + ## [0.7.8] - 2024-06-11 ### Added - Add new linking/unlink methods diff --git a/examples/apps/auth-sample/src/utils/openfortConfig.ts b/examples/apps/auth-sample/src/utils/openfortConfig.ts index e70cb27..432eb1b 100644 --- a/examples/apps/auth-sample/src/utils/openfortConfig.ts +++ b/examples/apps/auth-sample/src/utils/openfortConfig.ts @@ -8,6 +8,7 @@ const openfort = new Openfort({ shieldConfiguration: { shieldPublishableKey: process.env.NEXT_PUBLIC_SHIELD_API_KEY!, shieldEncryptionKey: process.env.NEXT_PUBLIC_SHIELD_ENCRYPTION_SHARE!, + debug: true, } }); diff --git a/packages/platform-bridge/src/bridge.ts b/packages/platform-bridge/src/bridge.ts index 0e0ecdd..ec62e8b 100644 --- a/packages/platform-bridge/src/bridge.ts +++ b/packages/platform-bridge/src/bridge.ts @@ -319,6 +319,7 @@ window.callFunction = async (jsonData: string) => { request.transactionIntentId, request.userOperationHash, request.signature, + request.optimistic, ); callbackToGame({ diff --git a/sdk/src/authManager.ts b/sdk/src/authManager.ts index 19687f9..485869f 100644 --- a/sdk/src/authManager.ts +++ b/sdk/src/authManager.ts @@ -4,12 +4,12 @@ import { import { BackendApiClients } from '@openfort/openapi-clients'; import * as crypto from 'crypto'; import { - Auth, InitAuthResponse, InitializeOAuthOptions, JWK, SIWEInitResponse, + Auth, InitAuthResponse, InitializeOAuthOptions, SIWEInitResponse, AuthPlayerResponse, AuthResponse, OAuthProvider, ThirdPartyOAuthProvider, TokenType, CodeChallengeMethodEnum, } from './types'; import { SDKConfiguration } from './config'; -import { OpenfortErrorType, withOpenfortError } from './errors/openfortError'; +import { OpenfortError, OpenfortErrorType, withOpenfortError } from './errors/openfortError'; import InstanceManager from './instanceManager'; import { isBrowser } from './utils/helpers'; import DeviceCredentialsManager from './utils/deviceCredentialsManager'; @@ -293,11 +293,15 @@ export default class AuthManager { }, OpenfortErrorType.USER_REGISTRATION_ERROR); } - public async validateCredentials( - accessToken: string, - refreshToken: string, - jwk: JWK, - ): Promise { + public async validateCredentials(): Promise { + const jwk = await this.instanceManager.getJWK(); + const accessToken = this.instanceManager.getAccessToken()?.token; + const refreshToken = this.instanceManager.getRefreshToken(); + + if (!accessToken || !refreshToken || !jwk) { + throw new OpenfortError('Must be logged in to validate and refresh token', OpenfortErrorType.NOT_LOGGED_IN_ERROR); + } + try { const key = (await importJWK( { diff --git a/sdk/src/iframe/types.ts b/sdk/src/iframe/types.ts index 3354d9f..0e67b85 100644 --- a/sdk/src/iframe/types.ts +++ b/sdk/src/iframe/types.ts @@ -338,13 +338,17 @@ export class UpdateAuthenticationRequest implements IEventRequest { } export interface ShieldAuthentication { - auth: AuthType; + // Whether its using Openfort (either third-party or not) or a custom provider to verify the access token + auth: ShieldAuthType; + // The auth token, either idToken or accessToken token: string; + // When using a third party auth provider, the provider name authProvider?: string; + // When using a third party auth provider, the token type tokenType?: string; } -export enum AuthType { +export enum ShieldAuthType { OPENFORT = 'openfort', CUSTOM = 'custom', } diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 287dd0f..89f369d 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -13,8 +13,9 @@ export { SessionResponse, TransactionIntentResponse, SDKOverrides, + AuthType, } from './types'; -export { ShieldAuthentication, AuthType } from './iframe/types'; +export { ShieldAuthentication, ShieldAuthType } from './iframe/types'; export { Provider } from './evm/types'; export { SDKConfiguration, OpenfortConfiguration, ShieldConfiguration } from './config'; export { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer'; diff --git a/sdk/src/instanceManager.ts b/sdk/src/instanceManager.ts index b62601c..2dbd5c9 100644 --- a/sdk/src/instanceManager.ts +++ b/sdk/src/instanceManager.ts @@ -16,6 +16,8 @@ import { signerTypeStorageKey, thirdPartyProviderStorageKey, thirdPartyProviderTokenTypeStorageKey, + shieldAuthTypeStorageKey, + shieldAuthTokenStorageKey, } from './storage/storage'; export type AccessToken = { @@ -37,6 +39,8 @@ export default class InstanceManager { private deviceID: string | null = null; + private shieldAuthType: string | null = null; + private chainId: string | null = null; private accountAddress: string | null = null; @@ -213,10 +217,11 @@ export default class InstanceManager { const response = await this.backendApiClients.authenticationApi.getJwks(request); if (response.data.keys.length === 0) { - throw new Error('No keys found'); + throw new Error('Internal error: No JWKS keys found'); } const jwtKey = response.data.keys[0]; + this.setJWK(jwtKey); this.jwk = { kty: jwtKey.kty, crv: jwtKey.crv, @@ -318,4 +323,38 @@ export default class InstanceManager { this.accountType = null; this.persistentStorage.remove(accountTypeStorageKey); } + + public setShieldAuthType(accountType: string): void { + this.accountType = accountType; + this.persistentStorage.save(shieldAuthTypeStorageKey, accountType); + } + + public getShieldAuthType(): string | null { + if (!this.accountType) { + this.accountType = this.persistentStorage.get(shieldAuthTypeStorageKey); + } + return this.accountType; + } + + public removeShieldAuthType(): void { + this.accountType = null; + this.persistentStorage.remove(shieldAuthTypeStorageKey); + } + + public setShieldAuthToken(accountType: string): void { + this.accountType = accountType; + this.persistentStorage.save(shieldAuthTokenStorageKey, accountType); + } + + public getShieldAuthToken(): string | null { + if (!this.accountType) { + this.accountType = this.persistentStorage.get(shieldAuthTokenStorageKey); + } + return this.accountType; + } + + public removeShieldAuthToken(): void { + this.accountType = null; + this.persistentStorage.remove(shieldAuthTokenStorageKey); + } } diff --git a/sdk/src/openfort.ts b/sdk/src/openfort.ts index ff38515..d02c34b 100644 --- a/sdk/src/openfort.ts +++ b/sdk/src/openfort.ts @@ -17,6 +17,7 @@ import { AuthResponse, AuthPlayerResponse, OpenfortEventMap, + AuthType, } from './types'; import { SDKConfiguration } from './config'; import { EvmProvider } from './evm'; @@ -34,7 +35,7 @@ import { SessionStorage } from './storage/sessionStorage'; import IframeManager, { IframeConfiguration, } from './iframe/iframeManager'; -import { ShieldAuthentication } from './iframe/types'; +import { ShieldAuthType, ShieldAuthentication } from './iframe/types'; export class Openfort { private signer?: ISigner; @@ -98,6 +99,8 @@ export class Openfort { this.instanceManager.removeChainID(); this.instanceManager.removeDeviceID(); this.instanceManager.removeJWK(); + this.instanceManager.removeShieldAuthType(); + this.instanceManager.removeShieldAuthToken(); } } @@ -174,8 +177,14 @@ export class Openfort { shieldAuthentication?: ShieldAuthentication, recoveryPassword?: string, ): Promise { - const signer = this.newEmbeddedSigner(chainId, shieldAuthentication); await this.validateAndRefreshToken(); + if (shieldAuthentication) { + this.instanceManager.setShieldAuthType(shieldAuthentication?.auth); + if ((shieldAuthentication?.auth as ShieldAuthType) === ShieldAuthType.CUSTOM) { + this.instanceManager.setShieldAuthToken(shieldAuthentication?.token); + } + } + const signer = this.newEmbeddedSigner(chainId); await signer.ensureEmbeddedAccount(recoveryPassword); this.signer = signer; this.instanceManager.setSignerType(SignerType.EMBEDDED); @@ -308,6 +317,7 @@ export class Openfort { public async initOAuth( { provider, options }: { provider: OAuthProvider; options?: InitializeOAuthOptions }, ): Promise { + this.logout(); const authResponse = await this.authManager.initOAuth(provider, options); return authResponse; } @@ -453,7 +463,7 @@ export class Openfort { thirdPartyTokenType: null, }); this.instanceManager.setRefreshToken(auth.refreshToken); - this.instanceManager.setPlayerID(auth.player); + if (auth.player) this.instanceManager.setPlayerID(auth.player); } /** @@ -664,33 +674,34 @@ export class Openfort { /** * Validates and refreshes the access token if needed. */ - public async validateAndRefreshToken() { - if (!this.credentialsProvided()) { - return; - } - const accessToken = this.instanceManager.getAccessToken(); - const refreshToken = this.instanceManager.getRefreshToken(); - const jwk = await this.instanceManager.getJWK(); - - if (!accessToken || !refreshToken || !jwk) { - return; + public async validateAndRefreshToken():Promise { + const authType = this.credentialsProvided(); + if (!authType) { + throw new OpenfortError('Must be logged in to validate and refresh token', OpenfortErrorType.NOT_LOGGED_IN_ERROR); } - - const auth = await this.authManager.validateCredentials(accessToken.token, refreshToken, jwk); - if (auth.accessToken !== accessToken.token) { + if (authType === AuthType.OPENFORT) { + const auth = await this.authManager.validateCredentials(); this.storeCredentials(auth); - } - if (this.signer && this.signer.useCredentials()) { - await this.signer.updateAuthentication(); + if (this.signer && this.signer.useCredentials()) { + await this.signer.updateAuthentication(); + } } } - private credentialsProvided() { + private credentialsProvided(): AuthType | null { const token = this.instanceManager.getAccessToken(); const refreshToken = this.instanceManager.getRefreshToken(); - return token && ((token.token && token.thirdPartyProvider && token.thirdPartyTokenType) - || (token.token && refreshToken)); + if (!token) { + return null; + } + + if (token.token && token.thirdPartyProvider && token.thirdPartyTokenType) { + return AuthType.THIRD_PARTY; + } if (token.token && refreshToken) { + return AuthType.OPENFORT; + } + return null; } private async recoverSigner(): Promise { @@ -738,18 +749,35 @@ export class Openfort { private newEmbeddedSigner( chainId?: number, - shieldAuthentication?: ShieldAuthentication, ): EmbeddedSigner { if (!this.credentialsProvided()) { throw new OpenfortError('Must be logged in to configure embedded signer', OpenfortErrorType.NOT_LOGGED_IN_ERROR); } + let shieldAuthType = this.instanceManager.getShieldAuthType(); + if (!shieldAuthType) { + // TODO: remove, this is for backward compatibility + this.instanceManager.setShieldAuthType(ShieldAuthType.OPENFORT); + shieldAuthType = ShieldAuthType.OPENFORT; + // throw new OpenfortError('Shield auth type is not set', OpenfortErrorType.INVALID_CONFIGURATION); + } + const token = (shieldAuthType as ShieldAuthType) === ShieldAuthType.OPENFORT + ? this.instanceManager.getAccessToken()?.token + : this.instanceManager.getShieldAuthToken(); + if (!token) { + throw new OpenfortError('Shield auth token is not set', OpenfortErrorType.INVALID_CONFIGURATION); + } + const shieldAuth: ShieldAuthentication = { + auth: shieldAuthType as ShieldAuthType, + token, + }; + const iframeConfiguration: IframeConfiguration = { accessToken: this.instanceManager.getAccessToken()?.token ?? null, thirdPartyProvider: this.instanceManager.getAccessToken()?.thirdPartyProvider ?? null, thirdPartyTokenType: this.instanceManager.getAccessToken()?.thirdPartyTokenType ?? null, chainId: !chainId ? Number(this.instanceManager.getChainID()) ?? null : chainId, - recovery: shieldAuthentication ?? null, + recovery: shieldAuth, }; return new EmbeddedSigner(this.iframeManager, this.instanceManager, iframeConfiguration); } diff --git a/sdk/src/storage/storage.ts b/sdk/src/storage/storage.ts index 51f4b14..81b8103 100644 --- a/sdk/src/storage/storage.ts +++ b/sdk/src/storage/storage.ts @@ -10,6 +10,8 @@ export const chainIDStorageKey = 'openfort.chain_id'; export const jwksStorageKey = 'openfort.jwk'; export const deviceIDStorageKey = 'openfort.device_id'; export const accountTypeStorageKey = 'openfort.account_type'; +export const shieldAuthTypeStorageKey = 'openfort.shield_auth_type'; +export const shieldAuthTokenStorageKey = 'openfort.shield_auth_token'; export const accountAddressStorageKey = 'openfort.account_address'; export interface IStorage { diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 6a14d45..ac82401 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -29,8 +29,13 @@ export enum AccountType { UPGRADEABLE_V5 = 'Upgradeable_v05', } +export enum AuthType { + OPENFORT = 'openfort', + THIRD_PARTY = 'thirdParty', +} + export type Auth = { - player: string; + player?: string; accessToken: string; refreshToken: string; };