From 2a57dacbebbdeef7aae7d8057598d524d83d08e4 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Fri, 8 Mar 2024 18:06:32 +0100 Subject: [PATCH 01/13] feat: refactor unify sdk --- README.md | 87 ++++-------- package.json | 2 +- src/{utils => clients}/iframe-client.ts | 0 src/{ => crypto}/key-pair.ts | 27 +--- src/index.ts | 4 - src/openfort.ts | 181 ++++++++++++++++++++---- src/openfortAuth.ts | 28 +++- src/recovery/passwordRecovery.ts | 11 ++ src/recovery/recovery.ts | 3 + src/signer/embedded.signer.ts | 59 ++++++-- src/signer/session.signer.ts | 51 +++++-- src/signer/signer.ts | 11 +- src/storage/base-storage.ts | 7 - src/storage/local-storage.ts | 44 ++---- src/storage/storage-keys.ts | 4 - src/storage/storage.ts | 10 ++ src/version.ts | 2 +- 17 files changed, 352 insertions(+), 179 deletions(-) rename src/{utils => clients}/iframe-client.ts (100%) rename src/{ => crypto}/key-pair.ts (63%) create mode 100644 src/recovery/passwordRecovery.ts create mode 100644 src/recovery/recovery.ts delete mode 100644 src/storage/base-storage.ts delete mode 100644 src/storage/storage-keys.ts create mode 100644 src/storage/storage.ts diff --git a/README.md b/README.md index bf15d45..c2ea984 100644 --- a/README.md +++ b/README.md @@ -40,81 +40,50 @@ yarn add @openfort/openfort-js ## Usage -The package needs to be configured with your account's public key, which is -available in the [Openfort Dashboard][api-keys]. Require it with the key's -value: - -```js -import Openfort from '@openfort/openfort-js'; -const openfort = new Openfort('pk_test_...'); -``` -In order to sign messages, you have 4 options to choose from: -* Let Openfort handle the signing process, dont need to pass any signer to the Openfort instance. -* Sign yourself and pass the signature to Openfort, dont need to pass any signer to the Openfort instance. -* Use a Session Key to sign messages, you need to pass a SessionSigner to the Openfort instance. -* Use Embedded Signer to sign messages, you need to pass an Embedded Signer to the Openfort instance. - -#### Session Signer -```ts -const sessionSigner = new SessionSigner() -const openfort = new Openfort('pk_test_...', sessionSigner); -``` - -#### Embedded Signer -For the embedded signer, if your player has an account you can pass it to the embedded signer to use it. If the account is not provided, the embedded signer will check if the localstorage has a device which is already registered, if not, it will create a new device and store it in the localstorage. -For the recovery process, you can ask the user for a password to encrypt the recovery share. - -```ts -const embeddedSigner = new EmbeddedSigner('pk_test_...', 'acc_...', '********'); -const openfort = new Openfort('pk_test_...', embeddedSigner); -``` - - -### Create and store a new player session key - -1. Create a session key pair for the player: - +With the Openfort Unity SDK, you can sign transaction intents using one of four methods or signers: ```typescript -openfort.createSessionKey(); +const sdk = new Openfort("pk_test_XXXXXXX"); ``` -2. Save the generated session key pair on device: +### 1. Session Signer +The Session Signer allows you to use external signing keys, without needing to provide it every time. Here's how to use it: +- **Configure the Session Key**: Call `configureSessionKey()`. This method returns an Ethereum address and a boolean indicating whether you need to register the key from the backend. ```typescript -openfort.saveSessionKey(); +const sessionKey = sdk.configureSessionKey(); ``` +- **Register Key and Send Signature Session Request**: If `sessionKey.isRegistered` boolean is false, register the key from the backend. Refer to the documentation for [session management](https://www.openfort.xyz/docs/guides/accounts/sessions). +- **Send Signature Transaction Intent Request**: When calling sendSignatureTransactionIntentRequest, pass the transaction intent ID and the user operation hash. The session signer will handle the signing. + +### 2. External Sign -3. Authorize player with the game backend service and passing the address of the session key pair: +This method allows you to externally sign transaction intents without logging in or additional configurations: +- **Call SendSignatureTransactionIntentRequest**: Simply pass the transaction intent ID and the signature. ```typescript -const address = openfort.sessionKey.address -// API call to the game backend with the address to register it +const response = await sdk.sendSignatureTransactionIntentRequest("transactionIntentId", null, "signature"); ``` -#### Register the session key using a non-custodial signer - -If the Openfort account is owned by an external signer, the owner must use it to sign and approve the registration of the session key. The hash containing the message to be signed appears in [next_actions][next-action] from the create session request. - +### 3. Embedded Signer +The Embedded Signer uses SSS to manage the private key on the client side. To learn more, visit our [security documentation](https://www.openfort.xyz/docs/security). +- **Login and Configure the Embedded Signer**: First, ensure the user is logged in, using `LoginWithEmailPassword`, `LoginWithOAuth` or if not registred `SignUpWithEmailPassword`. Then call `ConfigureEmbeddedSigner`. If a `MissingRecoveryMethod` exception is thrown, it indicates there's no share on the device and you have to call `ConfigureEmbeddedRecovery` to provide a recovery method. ```typescript -// Sign the message with the signer -await openfort.sendSignatureSessionRequest( - session_id, - signed_message -); +try { + await sdk.loginWithEmailPassword("email", "password"); + sdk.configureEmbeddedSigner(chainId); +} catch (e) { + if (e instanceof MissingRecoveryMethod) { + await sdk.configureEmbeddedSignerRecovery(new PasswordRecovery("password")); + } +} ``` - -### Use the session key to sign a message - -The hash containing the message to be signed appears in [next_actions][next-action] from the create transactionIntent request. - +For now the only recovery method available is the `PasswordRecovery` method. +- **Send Signature Transaction Intent Request**: Similar to the session signer, pass the transaction intent ID and the user operation hash. The embedded signer reconstructs the key and signs the transaction. ```typescript -await openfort.signMessage(message); -await openfort.sendSignatureTransactionIntentRequest( - transactionIntent_id, - signed_message -); +const response = await sdk.sendSignatureTransactionIntentRequest("transactionIntentId", "userOp"); ``` + ## Usage examples - [Next.js application with non-custodial signer](https://github.com/openfort-xyz/samples/tree/main/rainbow-ssv-nextjs) - [Next.js application with custodial signer and social login](https://github.com/openfort-xyz/samples/tree/main/ssv-social-nextjs) diff --git a/package.json b/package.json index e2359f4..404bec1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openfort/openfort-js", - "version": "0.4.6", + "version": "0.5.0", "description": "", "author": "Openfort", "repository": { diff --git a/src/utils/iframe-client.ts b/src/clients/iframe-client.ts similarity index 100% rename from src/utils/iframe-client.ts rename to src/clients/iframe-client.ts diff --git a/src/key-pair.ts b/src/crypto/key-pair.ts similarity index 63% rename from src/key-pair.ts rename to src/crypto/key-pair.ts index 8683e80..df23a8a 100644 --- a/src/key-pair.ts +++ b/src/crypto/key-pair.ts @@ -1,14 +1,10 @@ import {secp256k1} from "@noble/curves/secp256k1"; -import {LocalStorage} from "./storage/local-storage"; -import {StorageKeys} from "./storage/storage-keys"; import {SigningKey} from "@ethersproject/signing-key"; import {arrayify, Bytes, BytesLike, joinSignature} from "@ethersproject/bytes"; import {computeAddress} from "@ethersproject/transactions"; import {hashMessage} from "@ethersproject/hash"; export class KeyPair extends SigningKey { - private static readonly storage = new LocalStorage(); - /** * Initialize keypair based on the private key, if it is provided or generate a brand new keypair. * @param privateKey Optional parameter to initialize private key from @@ -25,32 +21,21 @@ export class KeyPair extends SigningKey { return joinSignature(this.signDigest(hashMessage(arrayify(message)))); } - /** - * Save to the storage initialized as a static property of the KeyPair class - */ - public save(): void { - KeyPair.storage.save(StorageKeys.SESSION_KEY, this.privateKey); - } - - /** - * Remove the keypair from the storage - */ - public remove(): void { - KeyPair.storage.remove(StorageKeys.SESSION_KEY); - } - /** * Load private key from the storage and generate keypair based on it. */ - public static load(): KeyPair | null { - const privateKey = KeyPair.storage.get(StorageKeys.SESSION_KEY); + public static load(privateKey: string): KeyPair | null { return privateKey ? new KeyPair(arrayify(privateKey)) : null; } /** * Return the address for the keypair */ - public get address(): string { + public getPublicKey(): string { return computeAddress(this.privateKey); } + + public getPrivateKey(): string { + return this.privateKey; + } } diff --git a/src/index.ts b/src/index.ts index f5a2b9c..7dc58da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,6 @@ import Openfort from "./openfort"; -export * from "./key-pair"; export * from "./openfort"; -export * from "./openfortAuth"; export * from "./signer/signer"; -export * from "./signer/session.signer"; -export * from "./signer/embedded.signer"; export default Openfort; diff --git a/src/openfort.ts b/src/openfort.ts index f457584..1cfad5c 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -1,55 +1,122 @@ import { - Configuration, + Configuration, OAuthProvider, SessionResponse, SessionsApi, TransactionIntentResponse, TransactionIntentsApi, } from "./generated"; -import {Signer} from "./signer/signer"; +import {ISigner, SignerType} from "./signer/signer"; +import {Auth, OpenfortAuth} from "./openfortAuth"; +import {AuthTokenStorageKey, IStorage, PlayerIDStorageKey, RefreshTokenStorageKey} from "./storage/storage"; +import {LocalStorage} from "./storage/local-storage"; +import {SessionSigner} from "./signer/session.signer"; +import {EmbeddedSigner} from "./signer/embedded.signer"; +import {IRecovery} from "./recovery/recovery"; +import {PasswordRecovery} from "./recovery/passwordRecovery"; export default class Openfort { - private readonly _configuration: Configuration; + private _signer?: ISigner; + private readonly _publishableKey: string; + private readonly _openfortAuth: OpenfortAuth; + private readonly _storage: IStorage; + private _sessionsApi?: SessionsApi; private _transactionsApi?: TransactionIntentsApi; - private readonly _signer?: Signer; - constructor(publishableKey: string, signer: Signer = undefined, basePath: string = undefined) { - this._configuration = new Configuration({accessToken: publishableKey, basePath}); + constructor(publishableKey: string, basePath: string = undefined) { + this._publishableKey = publishableKey; + this._openfortAuth = new OpenfortAuth(publishableKey, basePath); + this._storage = new LocalStorage(); + const configuration = new Configuration({accessToken: publishableKey, basePath}); + this._sessionsApi = new SessionsApi(configuration); + this._transactionsApi = new TransactionIntentsApi(configuration); + } + + public configureSessionKey(): SessionKey { + const signer = new SessionSigner(this._storage); this._signer = signer; + + const publicKey = signer.loadKeys(); + if (!publicKey) { + const newPublicKey = signer.generateKeys(); + return {publicKey: newPublicKey, isRegistered: false}; + } + + return {publicKey, isRegistered: true}; } - protected get sessionsApi(): SessionsApi { - if (!this._sessionsApi) { - this._sessionsApi = new SessionsApi(this._configuration); + public configureEmbeddedSigner(chainId: number): void { + if (!this.isLoggedIn()) { + throw new NotLoggedIn("Must be logged in to configure embedded signer"); + } + + const signer = new EmbeddedSigner(chainId, this._publishableKey, this._storage); + this._signer = signer; + + if (!signer.IsLoaded()) { + throw new MissingRecoveryMethod("This device has not been configured, in order to recover your account or create a new one you must provide recovery method"); } - return this._sessionsApi; } - protected get transactionsApi(): TransactionIntentsApi { - if (!this._transactionsApi) { - this._transactionsApi = new TransactionIntentsApi(this._configuration); + public async configureEmbeddedSignerRecovery(recovery: IRecovery): Promise { + if (!this._signer) { + throw new EmbeddedNotConfigured("No embedded signer configured"); } - return this._transactionsApi; + + if (this._signer.getSingerType() !== SignerType.EMBEDDED) { + throw new EmbeddedNotConfigured("Signer must be embedded signer"); + } + + const embeddedSigner = (this._signer as EmbeddedSigner); + embeddedSigner.setRecovery(recovery); + + await this.validateAndRefreshToken(); + await embeddedSigner.ensureEmbeddedAccount(); + } + + public async loginWithEmailPassword(email: string, password: string): Promise { + const result = await this._openfortAuth.authorizeWithEmailPassword(email, password); + this.storeCredentials(result); + return result.accessToken; + } + + public async loginWithOAuthToken(provider: OAuthProvider, token: string): Promise { + const result = await this._openfortAuth.authorizeWithOAuthToken(provider, token); + this.storeCredentials(result); + return result.accessToken; + } + + public async signUpWithEmailPassword(email: string, password: string): Promise { + const result = await this._openfortAuth.signUp(email, password); + this.storeCredentials(result); + return result.accessToken; + } + + private storeCredentials(auth: Auth): void { + this._storage.save(AuthTokenStorageKey, auth.accessToken); + this._storage.save(RefreshTokenStorageKey, auth.refreshToken); + this._storage.save(PlayerIDStorageKey, auth.player); } public async sendSignatureTransactionIntentRequest( transactionIntentId: string, - userOp?: string, - signature?: string, - optimistic?: boolean, + userOp: string = null, + signature: string = null, ): Promise { - if (!signature && userOp) { + if (!signature) { + if (!userOp) { + throw new NothingToSign("No user operation or signature provided"); + } + if (!this._signer) { - throw new Error("No signer nor signature provided"); + throw new NoSignerConfigured("In order to sign a transaction intent, a signer must be configured"); } + await this.validateAndRefreshToken(); signature = await this._signer.sign(userOp); } - if (!signature) { - throw new Error("No signature provided"); - } - const result = await this.transactionsApi.signature(transactionIntentId, {signature, optimistic}); + const result = await this._transactionsApi.signature(transactionIntentId, {signature}); return result.data; } @@ -65,7 +132,73 @@ export default class Openfort { signature = await this._signer.sign(sessionId); } - const result = await this.sessionsApi.signatureSession(sessionId, {signature, optimistic}); + const result = await this._sessionsApi.signatureSession(sessionId, {signature, optimistic}); return result.data; } + + private isLoggedIn() { + const token = this._storage.get(AuthTokenStorageKey); + const refreshToken = this._storage.get(RefreshTokenStorageKey); + const playerId = this._storage.get(PlayerIDStorageKey); + return token && refreshToken && playerId; + } + + private async validateAndRefreshToken() { + if (!this.isLoggedIn()) { + return; + } + + const auth = await this._openfortAuth.verifyAndRefreshToken(this._storage.get(AuthTokenStorageKey), this._storage.get(RefreshTokenStorageKey)); + if (auth.accessToken !== this._storage.get(AuthTokenStorageKey)) { + this.storeCredentials(auth); + } + if (this._signer && this._signer.useCredentials()) { + this._signer.updateAuthentication(); + } + } } + +export type SessionKey = { + publicKey: string; + isRegistered: boolean; +} + +class NotLoggedIn extends Error { + constructor(message: string) { + super(message); + this.name = "NotLoggedIn"; + Object.setPrototypeOf(this, NotLoggedIn.prototype); + } +} + +class MissingRecoveryMethod extends Error { + constructor(message: string) { + super(message); + this.name = "MissingRecoveryMethod"; + Object.setPrototypeOf(this, MissingRecoveryMethod.prototype); + } +} + +class EmbeddedNotConfigured extends Error { + constructor(message: string) { + super(message); + this.name = "EmbeddedNotConfigured"; + Object.setPrototypeOf(this, EmbeddedNotConfigured.prototype); + } +} + +class NoSignerConfigured extends Error { + constructor(message: string) { + super(message); + this.name = "NoSignerConfigured"; + Object.setPrototypeOf(this, NoSignerConfigured.prototype); + } +} + +class NothingToSign extends Error { + constructor(message: string) { + super(message); + this.name = "NothingToSign"; + Object.setPrototypeOf(this, NothingToSign.prototype); + } +} \ No newline at end of file diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index a4fc127..ccaa0b8 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -1,4 +1,4 @@ -import {Configuration, OAuthProvider, AuthenticationApi, AuthResponse} from "./generated"; +import {Configuration, OAuthProvider, AuthenticationApi} from "./generated"; import {errors, importJWK, jwtVerify} from "jose"; export type Auth = { @@ -18,9 +18,31 @@ export class OpenfortAuth { this._publishableKey = publishableKey; } - public async authorizeWithOAuthToken(provider: OAuthProvider, token: string): Promise { + public async authorizeWithOAuthToken(provider: OAuthProvider, token: string): Promise { const result = await this._oauthApi.authenticateOAuth({provider, token}); - return result.data; + return { + player: result.data.player.id, + accessToken: result.data.token, + refreshToken: result.data.refreshToken, + }; + } + + public async authorizeWithEmailPassword(email: string, password: string): Promise { + const result = await this._oauthApi.loginEmailPassword({email, password}); + return { + player: result.data.player.id, + accessToken: result.data.token, + refreshToken: result.data.refreshToken, + }; + } + + public async signUp(email: string, password: string): Promise { + const result = await this._oauthApi.signupEmailPassword({name: "", email, password}); + return { + player: result.data.player.id, + accessToken: result.data.token, + refreshToken: result.data.refreshToken, + }; } public async verifyAndRefreshToken(token: string, refreshToken: string): Promise { diff --git a/src/recovery/passwordRecovery.ts b/src/recovery/passwordRecovery.ts new file mode 100644 index 0000000..1c8bf8d --- /dev/null +++ b/src/recovery/passwordRecovery.ts @@ -0,0 +1,11 @@ +import {IRecovery} from "./recovery"; + +export class PasswordRecovery implements IRecovery { + private readonly recoveryPassword: string; + constructor(recoveryPassword: string) { + this.recoveryPassword = recoveryPassword; + } + public getRecoveryPassword(): string { + return this.recoveryPassword; + } +} \ No newline at end of file diff --git a/src/recovery/recovery.ts b/src/recovery/recovery.ts new file mode 100644 index 0000000..934895b --- /dev/null +++ b/src/recovery/recovery.ts @@ -0,0 +1,3 @@ +export interface IRecovery { + getRecoveryPassword(): string; +} \ No newline at end of file diff --git a/src/signer/embedded.signer.ts b/src/signer/embedded.signer.ts index 71f3a24..8687f21 100644 --- a/src/signer/embedded.signer.ts +++ b/src/signer/embedded.signer.ts @@ -1,21 +1,49 @@ -import {IframeClient} from "../utils/iframe-client"; +import {IframeClient} from "../clients/iframe-client"; import {Bytes} from "@ethersproject/bytes"; -import {Signer} from "./signer"; +import {ISigner, SignerType} from "./signer"; +import {AuthTokenStorageKey, IStorage} from "../storage/storage"; +import {IRecovery} from "../recovery/recovery"; -export class EmbeddedSigner implements Signer { - private readonly _iframeClient: IframeClient; +export class EmbeddedSigner implements ISigner { + private _iframeClient: IframeClient; private readonly _recoverySharePassword?: string; private _deviceID: string | null = null; + private readonly _publishableKey: string; + private readonly _chainId: number; + private readonly _iframeURL: string | null; + private readonly _storage: IStorage; + private _recovery: IRecovery; constructor( chainId: number, publishableKey: string, - accessToken: string, - recoverySharePassword?: string, + storage: IStorage, iframeURL?: string, ) { - this._iframeClient = new IframeClient(publishableKey, accessToken, chainId, iframeURL); - this._recoverySharePassword = recoverySharePassword; + this._storage = storage; + this._publishableKey = publishableKey; + this._chainId = chainId; + this._iframeURL = iframeURL; + this.configureIframeClient(); + } + + logout(): void { + this.dispose(); + } + useCredentials(): boolean { + return true; + } + updateAuthentication(): void { + this.dispose(); + this.configureIframeClient(); + } + + private configureIframeClient(): void { + this._iframeClient = new IframeClient(this._publishableKey, this._storage.get(AuthTokenStorageKey), this._chainId, this._iframeURL); + } + + getSingerType(): SignerType { + return SignerType.EMBEDDED; } public async ensureEmbeddedAccount(): Promise { @@ -28,17 +56,26 @@ export class EmbeddedSigner implements Signer { return this._deviceID; } - return await this._iframeClient.createAccount(this._recoverySharePassword); + if (!this._recovery) { + throw new Error("Recovery is not set"); + } + return await this._iframeClient.createAccount(this._recovery.getRecoveryPassword()); } public async sign(message: Bytes | string): Promise { - console.log("Signing message", message); await this.ensureEmbeddedAccount(); - console.log("Signing message after account creation", message); return await this._iframeClient.sign(message as string); } public dispose(): void { this._iframeClient.dispose(); } + + public setRecovery(recovery: IRecovery): void { + this._recovery = recovery; + } + + async IsLoaded(): Promise { + return this._deviceID !== null || await this._iframeClient.getCurrentDevice() !== ""; + } } diff --git a/src/signer/session.signer.ts b/src/signer/session.signer.ts index c41e791..920f8ff 100644 --- a/src/signer/session.signer.ts +++ b/src/signer/session.signer.ts @@ -1,16 +1,14 @@ -import {KeyPair} from "../key-pair"; +import {KeyPair} from "../crypto/key-pair"; import {Bytes} from "@ethersproject/bytes"; -import {Signer} from "./signer"; +import {ISigner, SignerType} from "./signer"; +import {IStorage, SessionKeyStorageKey} from "../storage/storage"; -export class SessionSigner implements Signer { - private readonly _sessionKey: KeyPair; +export class SessionSigner implements ISigner { + private _sessionKey: KeyPair; + private readonly _storage: IStorage; - constructor() { - this._sessionKey = KeyPair.load(); - if (!this._sessionKey) { - this._sessionKey = new KeyPair(); - this._sessionKey.save(); - } + constructor(storage: IStorage) { + this._storage = storage; } public sign(message: Bytes | string): Promise { @@ -18,4 +16,37 @@ export class SessionSigner implements Signer { resolve(this._sessionKey.sign(message)); }); } + + logout(): void { + this._storage.remove(SessionKeyStorageKey); + } + loadKeys(): string { + if (this._sessionKey !== null) { + return this._sessionKey.getPublicKey(); + } + + const sessionKey = this._storage.get(SessionKeyStorageKey); + if (!sessionKey) { + return null; + } + + this._sessionKey = KeyPair.load(sessionKey); + return this._sessionKey.getPublicKey(); + } + + generateKeys(): string { + this._sessionKey = new KeyPair(); + this._storage.save(SessionKeyStorageKey, this._sessionKey.getPrivateKey()); + return this._sessionKey.getPublicKey(); + } + + getSingerType(): SignerType { + return SignerType.SESSION; + } + + useCredentials(): boolean { + return false; + } + updateAuthentication(): void {return;} + } diff --git a/src/signer/signer.ts b/src/signer/signer.ts index 0e49f18..931f510 100644 --- a/src/signer/signer.ts +++ b/src/signer/signer.ts @@ -1,5 +1,14 @@ import {Bytes} from "@ethersproject/bytes"; -export interface Signer { +export enum SignerType { + EMBEDDED = "embedded", + SESSION = "session", +} + +export interface ISigner { sign(message: Bytes | string): Promise; + logout(): void; + useCredentials(): boolean; + updateAuthentication(): void; + getSingerType(): SignerType; } diff --git a/src/storage/base-storage.ts b/src/storage/base-storage.ts deleted file mode 100644 index 6a6a97e..0000000 --- a/src/storage/base-storage.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {StorageKeys} from "./storage-keys"; - -export interface BaseStorage { - get(key: StorageKeys): string; - save(key: StorageKeys, value: string): void; - remove(key: StorageKeys): void; -} diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index 50925fd..98ca6ed 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -1,42 +1,20 @@ -import {BaseStorage} from "./base-storage"; -import {StorageKeys} from "./storage-keys"; +import {IStorage} from "./storage"; -export class LocalStorage implements BaseStorage { - private static readonly _prefix = "OPENFORT"; - private static readonly _separator = "/"; - - public constructor(private readonly name?: string) {} - - public static get isAvailable(): boolean { - return "localStorage" in global && !!global.localStorage; - } - - private formatKey(key: StorageKeys): string { - return [LocalStorage._prefix, this.name, key].filter((n) => n).join(LocalStorage._separator); - } - - private static get localStorage(): LocalStorageInterface { - if (LocalStorage.isAvailable) { - return global.localStorage as LocalStorageInterface; +export class LocalStorage implements IStorage { + constructor() { + if (!localStorage) { + throw new Error("Local storage is not available"); } - throw Error("Local storage is not available in the current context"); } - - public get(key: StorageKeys): string { - return LocalStorage.localStorage.getItem(this.formatKey(key)); + public get(key: string): string { + return localStorage.getItem(key); } - public save(key: StorageKeys, value: string): void { - LocalStorage.localStorage.setItem(this.formatKey(key), value); + public save(key: string, value: string): void { + localStorage.setItem(key, value); } - public remove(key: StorageKeys): void { - LocalStorage.localStorage.removeItem(this.formatKey(key)); + public remove(key: string): void { + localStorage.removeItem(key); } } - -interface LocalStorageInterface { - getItem(key: string): string | null; - setItem(key: string, value: string): void; - removeItem(key: string): void; -} diff --git a/src/storage/storage-keys.ts b/src/storage/storage-keys.ts deleted file mode 100644 index 73ce9c9..0000000 --- a/src/storage/storage-keys.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum StorageKeys { - SESSION_KEY = "SESSION-KEY", - SESSION_ID = "SESSION-ID", -} diff --git a/src/storage/storage.ts b/src/storage/storage.ts new file mode 100644 index 0000000..3acbf92 --- /dev/null +++ b/src/storage/storage.ts @@ -0,0 +1,10 @@ +export const AuthTokenStorageKey = "openfort.auth_token"; +export const RefreshTokenStorageKey = "openfort.refresh_token"; +export const PlayerIDStorageKey = "openfort.player_id"; +export const SessionKeyStorageKey = "openfort.session_key"; + +export interface IStorage { + get(key: string): string; + save(key: string, value: string): void; + remove(key: string): void; +} diff --git a/src/version.ts b/src/version.ts index 8d168bd..24ef830 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ -export const VERSION = "0.4.6"; +export const VERSION = "0.5.0"; export const PACKAGE = "@openfort/openfort-js"; From fce4379566c1a8b72a868139dea6449d4ddca4e6 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Fri, 8 Mar 2024 18:06:35 +0100 Subject: [PATCH 02/13] feat: refactor unify sdk --- src/index.ts | 3 ++- src/openfort.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7dc58da..ead132b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import Openfort from "./openfort"; export * from "./openfort"; -export * from "./signer/signer"; +export * from "./recovery/passwordRecovery"; +export {OAuthProvider, TransactionIntentResponse, SessionResponse} from "./generated/api"; export default Openfort; diff --git a/src/openfort.ts b/src/openfort.ts index 1cfad5c..475ae52 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -12,7 +12,6 @@ import {LocalStorage} from "./storage/local-storage"; import {SessionSigner} from "./signer/session.signer"; import {EmbeddedSigner} from "./signer/embedded.signer"; import {IRecovery} from "./recovery/recovery"; -import {PasswordRecovery} from "./recovery/passwordRecovery"; export default class Openfort { private _signer?: ISigner; From f4d5472165bb04c5e01c3bbf0f64b8dcea537ad4 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Sun, 10 Mar 2024 08:44:48 +0100 Subject: [PATCH 03/13] fix: localstorage check --- src/storage/local-storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index 98ca6ed..22fa263 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -2,7 +2,7 @@ import {IStorage} from "./storage"; export class LocalStorage implements IStorage { constructor() { - if (!localStorage) { + if (!("localStorage" in global && !!global.localStorage)) { throw new Error("Local storage is not available"); } } From c07b3f89f273923ca4dfb8591aa63e4159c02280 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Sun, 10 Mar 2024 08:51:40 +0100 Subject: [PATCH 04/13] chore: add logout option --- src/openfort.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/openfort.ts b/src/openfort.ts index 475ae52..6d6da69 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -31,6 +31,15 @@ export default class Openfort { this._transactionsApi = new TransactionIntentsApi(configuration); } + public logout(): void { + this._storage.remove(AuthTokenStorageKey); + this._storage.remove(RefreshTokenStorageKey); + this._storage.remove(PlayerIDStorageKey); + if (this._signer) { + this._signer.logout(); + } + } + public configureSessionKey(): SessionKey { const signer = new SessionSigner(this._storage); this._signer = signer; From 285fdb56546c3230901fdeea450db5f4e93e166d Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 11:04:28 +0100 Subject: [PATCH 05/13] fix: localstorage check on use and avoid multiple iframes --- .DS_Store | Bin 0 -> 6148 bytes package.json | 2 +- src/.DS_Store | Bin 0 -> 6148 bytes src/clients/iframe-client.ts | 8 ++++++++ src/openfort.ts | 4 ++-- src/storage/local-storage.ts | 6 ++++++ src/version.ts | 2 +- 7 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c88a062b05be4fd1d362b3e4c6a7481e718b69d0 GIT binary patch literal 6148 zcmeH~O$x$5422WzLU7Zi%h`AUZ!n0Spcima5J4*Vx1OW>k_m#k8KJGkN^pg011%5 z4-v3?8#bF)Wh4O-Ab}?V`#vPNX$~z_{nLTqBLK8P*$r!-C7{U)&>UK-q5{*H9yD6j z#}KP~J2b_)99pW@cF`C;crrWIXQgOGwy`I%~QMGk}L;X0y%TE9jyNVZZH|!@{KyzrRiVBQB0*--!1inh( E0rZ6u#{d8T literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 404bec1..551dfe3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openfort/openfort-js", - "version": "0.5.0", + "version": "0.5.1", "description": "", "author": "Openfort", "repository": { diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..534a05e97e5fc094322ab645ac2ae731ee20e840 GIT binary patch literal 6148 zcmeHLze@u#6n?3-wsk3p;21|Y{Ri|cC%6~E(H|{UXuV=pP!vvGocs&K(aBX^)Xh!t zA8>Q_KXCASNvcWjTAZXv9wgu0%gf7m-{q2~L?p_))fu7^5#>=Bvqe-X#_e1ZmNA|Q zpinusXouR3`b=d#N<@2ySHLSUXbSMRJ3{N!piQcg_5C(mn(FvxGNKuU;aVeFLrebc zX!7>t^=UpWs``tl)&{Q@ozW!HP!4>yX`fnHO(MOTblQh!yW4FpSB1sHFI=}|VP^ZG$%?pbk4$!9a`AL;Nbt}wqmc-5d) zM0-Fe=CC7imKI}k3&)nP@3dZtTsE_QaSkt%%qvH2%%(bK6Ii*NN=x1CHlN3ji}FL~ z{aHC|F1=!09@ETY1UxW<)Z4?GbeRmDCL?Sv)%VNNVP{@Dd2BYlqg+0c`3#z2&t}Vy zYV@^Nz$@St_*Q_|2Z_QM7>qTlr2~~Z0symcE5o(gJAslNz`$Uv5gwS(U4go*Fh>lb zyTdO{TwpNPsJoL;BO{L)S(p=wP@}^yRXB-2qp!UJUIABuA$?oo{ePkN`QPp2@4NzD zfq$id$cB}$j7KtiYvSQ}uazh(C~O=TYgAoOneA9z@K((KD=0&oNKj MGWg0X@TUrV0hw^vsQ>@~ literal 0 HcmV?d00001 diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index ec1c0e1..a25122b 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -6,11 +6,19 @@ export class IframeClient { if (!document) { throw new Error("must be run in a browser"); } + + const actualIframeURL = document.getElementById("openfort-iframe") + if (actualIframeURL) { + this._iframe = actualIframeURL as HTMLIFrameElement; + return + } + this._chainId = chainId; this._iframe = document.createElement("iframe"); const baseURL = iframeURL || "https://iframe.openfort.xyz"; this._iframe.src = baseURL + "/iframe?accessToken=" + accessToken + "&publishableKey=" + publishableKey; this._iframe.style.display = "none"; + this._iframe.id = "openfort-iframe"; document.body.appendChild(this._iframe); } diff --git a/src/openfort.ts b/src/openfort.ts index 6d6da69..03f8800 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -53,12 +53,12 @@ export default class Openfort { return {publicKey, isRegistered: true}; } - public configureEmbeddedSigner(chainId: number): void { + public configureEmbeddedSigner(chainId: number, iframeURL: string = undefined): void { if (!this.isLoggedIn()) { throw new NotLoggedIn("Must be logged in to configure embedded signer"); } - const signer = new EmbeddedSigner(chainId, this._publishableKey, this._storage); + const signer = new EmbeddedSigner(chainId, this._publishableKey, this._storage, iframeURL); this._signer = signer; if (!signer.IsLoaded()) { diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index 22fa263..a98bbcb 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -2,19 +2,25 @@ import {IStorage} from "./storage"; export class LocalStorage implements IStorage { constructor() { + } + + private validateLocalStorage(): void { if (!("localStorage" in global && !!global.localStorage)) { throw new Error("Local storage is not available"); } } public get(key: string): string { + this.validateLocalStorage(); return localStorage.getItem(key); } public save(key: string, value: string): void { + this.validateLocalStorage(); localStorage.setItem(key, value); } public remove(key: string): void { + this.validateLocalStorage(); localStorage.removeItem(key); } } diff --git a/src/version.ts b/src/version.ts index 24ef830..cbdf5d5 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ -export const VERSION = "0.5.0"; +export const VERSION = "0.5.1"; export const PACKAGE = "@openfort/openfort-js"; From 5a43a650993853d99ac7d550e880389e3470ecb7 Mon Sep 17 00:00:00 2001 From: Jaume Alavedra Date: Mon, 11 Mar 2024 11:08:49 +0100 Subject: [PATCH 06/13] format and lint --- package.json | 2 +- src/clients/iframe-client.ts | 4 ++-- src/openfort.ts | 18 ++++++++++++------ src/recovery/passwordRecovery.ts | 2 +- src/recovery/recovery.ts | 2 +- src/signer/embedded.signer.ts | 16 ++++++++-------- src/signer/session.signer.ts | 5 +++-- src/storage/local-storage.ts | 3 --- src/version.ts | 2 +- 9 files changed, 29 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 551dfe3..404bec1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openfort/openfort-js", - "version": "0.5.1", + "version": "0.5.0", "description": "", "author": "Openfort", "repository": { diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index a25122b..7fbef2b 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -7,10 +7,10 @@ export class IframeClient { throw new Error("must be run in a browser"); } - const actualIframeURL = document.getElementById("openfort-iframe") + const actualIframeURL = document.getElementById("openfort-iframe"); if (actualIframeURL) { this._iframe = actualIframeURL as HTMLIFrameElement; - return + return; } this._chainId = chainId; diff --git a/src/openfort.ts b/src/openfort.ts index 03f8800..707cd97 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -1,5 +1,6 @@ import { - Configuration, OAuthProvider, + Configuration, + OAuthProvider, SessionResponse, SessionsApi, TransactionIntentResponse, @@ -62,7 +63,9 @@ export default class Openfort { this._signer = signer; if (!signer.IsLoaded()) { - throw new MissingRecoveryMethod("This device has not been configured, in order to recover your account or create a new one you must provide recovery method"); + throw new MissingRecoveryMethod( + "This device has not been configured, in order to recover your account or create a new one you must provide recovery method", + ); } } @@ -75,7 +78,7 @@ export default class Openfort { throw new EmbeddedNotConfigured("Signer must be embedded signer"); } - const embeddedSigner = (this._signer as EmbeddedSigner); + const embeddedSigner = this._signer as EmbeddedSigner; embeddedSigner.setRecovery(recovery); await this.validateAndRefreshToken(); @@ -156,7 +159,10 @@ export default class Openfort { return; } - const auth = await this._openfortAuth.verifyAndRefreshToken(this._storage.get(AuthTokenStorageKey), this._storage.get(RefreshTokenStorageKey)); + const auth = await this._openfortAuth.verifyAndRefreshToken( + this._storage.get(AuthTokenStorageKey), + this._storage.get(RefreshTokenStorageKey), + ); if (auth.accessToken !== this._storage.get(AuthTokenStorageKey)) { this.storeCredentials(auth); } @@ -169,7 +175,7 @@ export default class Openfort { export type SessionKey = { publicKey: string; isRegistered: boolean; -} +}; class NotLoggedIn extends Error { constructor(message: string) { @@ -209,4 +215,4 @@ class NothingToSign extends Error { this.name = "NothingToSign"; Object.setPrototypeOf(this, NothingToSign.prototype); } -} \ No newline at end of file +} diff --git a/src/recovery/passwordRecovery.ts b/src/recovery/passwordRecovery.ts index 1c8bf8d..4bf929e 100644 --- a/src/recovery/passwordRecovery.ts +++ b/src/recovery/passwordRecovery.ts @@ -8,4 +8,4 @@ export class PasswordRecovery implements IRecovery { public getRecoveryPassword(): string { return this.recoveryPassword; } -} \ No newline at end of file +} diff --git a/src/recovery/recovery.ts b/src/recovery/recovery.ts index 934895b..53f1f50 100644 --- a/src/recovery/recovery.ts +++ b/src/recovery/recovery.ts @@ -1,3 +1,3 @@ export interface IRecovery { getRecoveryPassword(): string; -} \ No newline at end of file +} diff --git a/src/signer/embedded.signer.ts b/src/signer/embedded.signer.ts index 8687f21..5ba247a 100644 --- a/src/signer/embedded.signer.ts +++ b/src/signer/embedded.signer.ts @@ -14,12 +14,7 @@ export class EmbeddedSigner implements ISigner { private readonly _storage: IStorage; private _recovery: IRecovery; - constructor( - chainId: number, - publishableKey: string, - storage: IStorage, - iframeURL?: string, - ) { + constructor(chainId: number, publishableKey: string, storage: IStorage, iframeURL?: string) { this._storage = storage; this._publishableKey = publishableKey; this._chainId = chainId; @@ -39,7 +34,12 @@ export class EmbeddedSigner implements ISigner { } private configureIframeClient(): void { - this._iframeClient = new IframeClient(this._publishableKey, this._storage.get(AuthTokenStorageKey), this._chainId, this._iframeURL); + this._iframeClient = new IframeClient( + this._publishableKey, + this._storage.get(AuthTokenStorageKey), + this._chainId, + this._iframeURL, + ); } getSingerType(): SignerType { @@ -76,6 +76,6 @@ export class EmbeddedSigner implements ISigner { } async IsLoaded(): Promise { - return this._deviceID !== null || await this._iframeClient.getCurrentDevice() !== ""; + return this._deviceID !== null || (await this._iframeClient.getCurrentDevice()) !== ""; } } diff --git a/src/signer/session.signer.ts b/src/signer/session.signer.ts index 920f8ff..fa22e5b 100644 --- a/src/signer/session.signer.ts +++ b/src/signer/session.signer.ts @@ -47,6 +47,7 @@ export class SessionSigner implements ISigner { useCredentials(): boolean { return false; } - updateAuthentication(): void {return;} - + updateAuthentication(): void { + return; + } } diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index a98bbcb..605a609 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -1,9 +1,6 @@ import {IStorage} from "./storage"; export class LocalStorage implements IStorage { - constructor() { - } - private validateLocalStorage(): void { if (!("localStorage" in global && !!global.localStorage)) { throw new Error("Local storage is not available"); diff --git a/src/version.ts b/src/version.ts index cbdf5d5..24ef830 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ -export const VERSION = "0.5.1"; +export const VERSION = "0.5.0"; export const PACKAGE = "@openfort/openfort-js"; From 270a8d6eac60a95546ef1a43b633386399c72924 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 11:43:45 +0100 Subject: [PATCH 07/13] chore: expose access token and checks if is authenticated/loaded --- src/clients/iframe-client.ts | 16 +++++++--------- src/openfort.ts | 25 +++++++++++++++++++++---- src/openfortAuth.ts | 22 +++++++++++++++++----- src/signer/embedded.signer.ts | 6 +++++- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index a25122b..8b0e3ad 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -22,16 +22,14 @@ export class IframeClient { document.body.appendChild(this._iframe); } - private waitForIframeLoad(): Promise { - if (!this._iframe.contentWindow) { - return new Promise((resolve) => { - this._iframe.onload = () => { - resolve(); - }; - }); - } + public isLoaded(): boolean { + return this._iframe.contentWindow !== null; + } - return Promise.resolve(); + private async waitForIframeLoad(): Promise { + while (!this.isLoaded()) { + await new Promise(resolve => setTimeout(resolve, 100)); + } } async createAccount(password?: string): Promise { diff --git a/src/openfort.ts b/src/openfort.ts index 03f8800..c70a676 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -54,14 +54,14 @@ export default class Openfort { } public configureEmbeddedSigner(chainId: number, iframeURL: string = undefined): void { - if (!this.isLoggedIn()) { + if (!this.isAuthenticated()) { throw new NotLoggedIn("Must be logged in to configure embedded signer"); } const signer = new EmbeddedSigner(chainId, this._publishableKey, this._storage, iframeURL); this._signer = signer; - if (!signer.IsLoaded()) { + if (!signer.isLoaded()) { throw new MissingRecoveryMethod("This device has not been configured, in order to recover your account or create a new one you must provide recovery method"); } } @@ -144,15 +144,32 @@ export default class Openfort { return result.data; } - private isLoggedIn() { + public isAuthenticated() { const token = this._storage.get(AuthTokenStorageKey); const refreshToken = this._storage.get(RefreshTokenStorageKey); const playerId = this._storage.get(PlayerIDStorageKey); return token && refreshToken && playerId; } + public getAccessToken(): string { + return this._storage.get(AuthTokenStorageKey); + } + + public isLoaded(): boolean { + if (!this._openfortAuth.getJwks()) { + return false; + } + + if (this._signer && this._signer.getSingerType() === SignerType.EMBEDDED) { + return (this._signer as EmbeddedSigner).iFrameLoaded(); + } + + return true; + } + + private async validateAndRefreshToken() { - if (!this.isLoggedIn()) { + if (!this.isAuthenticated()) { return; } diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index ccaa0b8..a5b67ad 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -1,5 +1,5 @@ import {Configuration, OAuthProvider, AuthenticationApi} from "./generated"; -import {errors, importJWK, jwtVerify} from "jose"; +import {errors, importJWK, jwtVerify, KeyLike} from "jose"; export type Auth = { player: string; @@ -11,11 +11,20 @@ export class OpenfortAuth { private readonly _configuration: Configuration; private _oauthApi?: AuthenticationApi; private readonly _publishableKey: string; + private _jwks: KeyLike | Uint8Array; constructor(publishableKey: string, basePath?: string) { this._configuration = new Configuration({accessToken: publishableKey, basePath}); this._oauthApi = new AuthenticationApi(this._configuration); this._publishableKey = publishableKey; + + this.getJwks().then((jwtks) => { + this._jwks = jwtks; + }); + } + + public get jwks(): KeyLike | Uint8Array { + return this._jwks; } public async authorizeWithOAuthToken(provider: OAuthProvider, token: string): Promise { @@ -44,15 +53,15 @@ export class OpenfortAuth { refreshToken: result.data.refreshToken, }; } - - public async verifyAndRefreshToken(token: string, refreshToken: string): Promise { + + public async getJwks(): Promise { const jwtks = await this._oauthApi.getJwks(this._publishableKey); if (jwtks.data.keys.length === 0) { throw new Error("No keys found"); } const jwtKey = jwtks.data.keys[0]; - const ecPublicKey = await importJWK( + return await importJWK( { kty: jwtKey.kty, crv: jwtKey.crv, @@ -61,8 +70,11 @@ export class OpenfortAuth { }, jwtKey.alg, ); + } + + public async verifyAndRefreshToken(token: string, refreshToken: string): Promise { try { - const verification = await jwtVerify(token, ecPublicKey); + const verification = await jwtVerify(token, this._jwks); return { player: verification.payload.sub, accessToken: token, diff --git a/src/signer/embedded.signer.ts b/src/signer/embedded.signer.ts index 8687f21..363e772 100644 --- a/src/signer/embedded.signer.ts +++ b/src/signer/embedded.signer.ts @@ -75,7 +75,11 @@ export class EmbeddedSigner implements ISigner { this._recovery = recovery; } - async IsLoaded(): Promise { + async isLoaded(): Promise { return this._deviceID !== null || await this._iframeClient.getCurrentDevice() !== ""; } + + iFrameLoaded(): boolean { + return this._iframeClient.isLoaded(); + } } From 32446164a5431452eb5469adf7d62b5d3ce27756 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 11:47:01 +0100 Subject: [PATCH 08/13] chore: expose access token and checks if is authenticated/loaded --- src/clients/iframe-client.ts | 6 +++--- src/openfort.ts | 19 ++++++++++++------- src/openfortAuth.ts | 2 +- src/recovery/passwordRecovery.ts | 2 +- src/recovery/recovery.ts | 2 +- src/signer/embedded.signer.ts | 16 ++++++++-------- src/signer/session.signer.ts | 5 +++-- src/storage/local-storage.ts | 3 +-- 8 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index 8b0e3ad..b201936 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -7,10 +7,10 @@ export class IframeClient { throw new Error("must be run in a browser"); } - const actualIframeURL = document.getElementById("openfort-iframe") + const actualIframeURL = document.getElementById("openfort-iframe"); if (actualIframeURL) { this._iframe = actualIframeURL as HTMLIFrameElement; - return + return; } this._chainId = chainId; @@ -28,7 +28,7 @@ export class IframeClient { private async waitForIframeLoad(): Promise { while (!this.isLoaded()) { - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); } } diff --git a/src/openfort.ts b/src/openfort.ts index c70a676..f6f38b6 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -1,5 +1,6 @@ import { - Configuration, OAuthProvider, + Configuration, + OAuthProvider, SessionResponse, SessionsApi, TransactionIntentResponse, @@ -62,7 +63,9 @@ export default class Openfort { this._signer = signer; if (!signer.isLoaded()) { - throw new MissingRecoveryMethod("This device has not been configured, in order to recover your account or create a new one you must provide recovery method"); + throw new MissingRecoveryMethod( + "This device has not been configured, in order to recover your account or create a new one you must provide recovery method", + ); } } @@ -75,7 +78,7 @@ export default class Openfort { throw new EmbeddedNotConfigured("Signer must be embedded signer"); } - const embeddedSigner = (this._signer as EmbeddedSigner); + const embeddedSigner = this._signer as EmbeddedSigner; embeddedSigner.setRecovery(recovery); await this.validateAndRefreshToken(); @@ -167,13 +170,15 @@ export default class Openfort { return true; } - private async validateAndRefreshToken() { if (!this.isAuthenticated()) { return; } - const auth = await this._openfortAuth.verifyAndRefreshToken(this._storage.get(AuthTokenStorageKey), this._storage.get(RefreshTokenStorageKey)); + const auth = await this._openfortAuth.verifyAndRefreshToken( + this._storage.get(AuthTokenStorageKey), + this._storage.get(RefreshTokenStorageKey), + ); if (auth.accessToken !== this._storage.get(AuthTokenStorageKey)) { this.storeCredentials(auth); } @@ -186,7 +191,7 @@ export default class Openfort { export type SessionKey = { publicKey: string; isRegistered: boolean; -} +}; class NotLoggedIn extends Error { constructor(message: string) { @@ -226,4 +231,4 @@ class NothingToSign extends Error { this.name = "NothingToSign"; Object.setPrototypeOf(this, NothingToSign.prototype); } -} \ No newline at end of file +} diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index a5b67ad..a3beb2a 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -53,7 +53,7 @@ export class OpenfortAuth { refreshToken: result.data.refreshToken, }; } - + public async getJwks(): Promise { const jwtks = await this._oauthApi.getJwks(this._publishableKey); if (jwtks.data.keys.length === 0) { diff --git a/src/recovery/passwordRecovery.ts b/src/recovery/passwordRecovery.ts index 1c8bf8d..4bf929e 100644 --- a/src/recovery/passwordRecovery.ts +++ b/src/recovery/passwordRecovery.ts @@ -8,4 +8,4 @@ export class PasswordRecovery implements IRecovery { public getRecoveryPassword(): string { return this.recoveryPassword; } -} \ No newline at end of file +} diff --git a/src/recovery/recovery.ts b/src/recovery/recovery.ts index 934895b..53f1f50 100644 --- a/src/recovery/recovery.ts +++ b/src/recovery/recovery.ts @@ -1,3 +1,3 @@ export interface IRecovery { getRecoveryPassword(): string; -} \ No newline at end of file +} diff --git a/src/signer/embedded.signer.ts b/src/signer/embedded.signer.ts index 363e772..19b806b 100644 --- a/src/signer/embedded.signer.ts +++ b/src/signer/embedded.signer.ts @@ -14,12 +14,7 @@ export class EmbeddedSigner implements ISigner { private readonly _storage: IStorage; private _recovery: IRecovery; - constructor( - chainId: number, - publishableKey: string, - storage: IStorage, - iframeURL?: string, - ) { + constructor(chainId: number, publishableKey: string, storage: IStorage, iframeURL?: string) { this._storage = storage; this._publishableKey = publishableKey; this._chainId = chainId; @@ -39,7 +34,12 @@ export class EmbeddedSigner implements ISigner { } private configureIframeClient(): void { - this._iframeClient = new IframeClient(this._publishableKey, this._storage.get(AuthTokenStorageKey), this._chainId, this._iframeURL); + this._iframeClient = new IframeClient( + this._publishableKey, + this._storage.get(AuthTokenStorageKey), + this._chainId, + this._iframeURL, + ); } getSingerType(): SignerType { @@ -76,7 +76,7 @@ export class EmbeddedSigner implements ISigner { } async isLoaded(): Promise { - return this._deviceID !== null || await this._iframeClient.getCurrentDevice() !== ""; + return this._deviceID !== null || (await this._iframeClient.getCurrentDevice()) !== ""; } iFrameLoaded(): boolean { diff --git a/src/signer/session.signer.ts b/src/signer/session.signer.ts index 920f8ff..fa22e5b 100644 --- a/src/signer/session.signer.ts +++ b/src/signer/session.signer.ts @@ -47,6 +47,7 @@ export class SessionSigner implements ISigner { useCredentials(): boolean { return false; } - updateAuthentication(): void {return;} - + updateAuthentication(): void { + return; + } } diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index a98bbcb..7fa9eb2 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -1,8 +1,7 @@ import {IStorage} from "./storage"; export class LocalStorage implements IStorage { - constructor() { - } + constructor() {} private validateLocalStorage(): void { if (!("localStorage" in global && !!global.localStorage)) { From 62167f7417c736e38335f56a03b0cba7e6afc574 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 12:02:40 +0100 Subject: [PATCH 09/13] chore: expose access token and checks if is authenticated/loaded --- src/openfort.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openfort.ts b/src/openfort.ts index f6f38b6..126662d 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -159,7 +159,7 @@ export default class Openfort { } public isLoaded(): boolean { - if (!this._openfortAuth.getJwks()) { + if (!this._openfortAuth.jwks) { return false; } From a5ad728619ec8f584fe7187026e714220d9b82ab Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 12:53:44 +0100 Subject: [PATCH 10/13] chore: expose access token and checks if is authenticated/loaded --- src/generated/api.ts | 430 ++++++++++++++++++++++++++++++------------- src/openfort.ts | 12 +- src/openfortAuth.ts | 23 +++ 3 files changed, 333 insertions(+), 132 deletions(-) diff --git a/src/generated/api.ts b/src/generated/api.ts index 5c5f5d4..449a73f 100644 --- a/src/generated/api.ts +++ b/src/generated/api.ts @@ -2353,6 +2353,25 @@ export interface DeployRequest { */ 'policy': string; } +/** + * + * @export + * @interface DeprecatedAuthenticatedPlayerResponse + */ +export interface DeprecatedAuthenticatedPlayerResponse { + /** + * Player\'s identifier. + * @type {string} + * @memberof DeprecatedAuthenticatedPlayerResponse + */ + 'playerId': string; + /** + * JWT access token. + * @type {string} + * @memberof DeprecatedAuthenticatedPlayerResponse + */ + 'token': string; +} /** * * @export @@ -9674,6 +9693,132 @@ export const AdminAuthenticationApiAxiosParamCreator = function (configuration?: let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Verifies the token generated by Openfort Auth. + * @summary Verify auth token. + * @param {string} token Specifies the auth token. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + verifyAuthToken: async (token: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'token' is not null or undefined + assertParamExists('verifyAuthToken', 'token', token) + const localVarPath = `/iam/v1/verify`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication sk required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (token !== undefined) { + localVarQueryParameter['token'] = token; + } + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest + * @param {*} [options] Override http request option. + * @deprecated + * @throws {RequiredError} + */ + verifyOAuth: async (provider: OAuthProvider, oAuthRequest: OAuthRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'provider' is not null or undefined + assertParamExists('verifyOAuth', 'provider', provider) + // verify required parameter 'oAuthRequest' is not null or undefined + assertParamExists('verifyOAuth', 'oAuthRequest', oAuthRequest) + const localVarPath = `/iam/v1/oauth/{provider}/verify` + .replace(`{${"provider"}}`, encodeURIComponent(String(provider))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication sk required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(oAuthRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by oauth token. + * @param {AuthenticateOAuthRequest} authenticateOAuthRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + verifyOAuthToken: async (authenticateOAuthRequest: AuthenticateOAuthRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'authenticateOAuthRequest' is not null or undefined + assertParamExists('verifyOAuthToken', 'authenticateOAuthRequest', authenticateOAuthRequest) + const localVarPath = `/iam/v1/oauth/verify`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication sk required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(authenticateOAuthRequest, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -9707,7 +9852,7 @@ export const AdminAuthenticationApiFp = function(configuration?: Configuration) * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async deleteAuthPlayer(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async deleteAuthPlayer(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAuthPlayer(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9757,6 +9902,41 @@ export const AdminAuthenticationApiFp = function(configuration?: Configuration) const localVarAxiosArgs = await localVarAxiosParamCreator.listOAuthConfig(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * Verifies the token generated by Openfort Auth. + * @summary Verify auth token. + * @param {string} token Specifies the auth token. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async verifyAuthToken(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.verifyAuthToken(token, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest + * @param {*} [options] Override http request option. + * @deprecated + * @throws {RequiredError} + */ + async verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuth(provider, oAuthRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by oauth token. + * @param {AuthenticateOAuthRequest} authenticateOAuthRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuthToken(authenticateOAuthRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -9784,7 +9964,7 @@ export const AdminAuthenticationApiFactory = function (configuration?: Configura * @param {*} [options] Override http request option. * @throws {RequiredError} */ - deleteAuthPlayer(id: string, options?: any): AxiosPromise { + deleteAuthPlayer(id: string, options?: any): AxiosPromise { return localVarFp.deleteAuthPlayer(id, options).then((request) => request(axios, basePath)); }, /** @@ -9829,6 +10009,38 @@ export const AdminAuthenticationApiFactory = function (configuration?: Configura listOAuthConfig(options?: any): AxiosPromise { return localVarFp.listOAuthConfig(options).then((request) => request(axios, basePath)); }, + /** + * Verifies the token generated by Openfort Auth. + * @summary Verify auth token. + * @param {string} token Specifies the auth token. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + verifyAuthToken(token: string, options?: any): AxiosPromise { + return localVarFp.verifyAuthToken(token, options).then((request) => request(axios, basePath)); + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest + * @param {*} [options] Override http request option. + * @deprecated + * @throws {RequiredError} + */ + verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: any): AxiosPromise { + return localVarFp.verifyOAuth(provider, oAuthRequest, options).then((request) => request(axios, basePath)); + }, + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by oauth token. + * @param {AuthenticateOAuthRequest} authenticateOAuthRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: any): AxiosPromise { + return localVarFp.verifyOAuthToken(authenticateOAuthRequest, options).then((request) => request(axios, basePath)); + }, }; }; @@ -9912,6 +10124,44 @@ export class AdminAuthenticationApi extends BaseAPI { public listOAuthConfig(options?: AxiosRequestConfig) { return AdminAuthenticationApiFp(this.configuration).listOAuthConfig(options).then((request) => request(this.axios, this.basePath)); } + + /** + * Verifies the token generated by Openfort Auth. + * @summary Verify auth token. + * @param {string} token Specifies the auth token. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AdminAuthenticationApi + */ + public verifyAuthToken(token: string, options?: AxiosRequestConfig) { + return AdminAuthenticationApiFp(this.configuration).verifyAuthToken(token, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest + * @param {*} [options] Override http request option. + * @deprecated + * @throws {RequiredError} + * @memberof AdminAuthenticationApi + */ + public verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig) { + return AdminAuthenticationApiFp(this.configuration).verifyOAuth(provider, oAuthRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by oauth token. + * @param {AuthenticateOAuthRequest} authenticateOAuthRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AdminAuthenticationApi + */ + public verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig) { + return AdminAuthenticationApiFp(this.configuration).verifyOAuthToken(authenticateOAuthRequest, options).then((request) => request(this.axios, this.basePath)); + } } @@ -10425,16 +10675,21 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf }; }, /** - * Verifies the token generated by Openfort Auth. - * @summary Verify auth token. - * @param {string} token Specifies the auth token. + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - verifyAuthToken: async (token: string, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'token' is not null or undefined - assertParamExists('verifyAuthToken', 'token', token) - const localVarPath = `/iam/v1/verify`; + verifyOAuth: async (provider: OAuthProvider, oAuthRequest: OAuthRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'provider' is not null or undefined + assertParamExists('verifyOAuth', 'provider', provider) + // verify required parameter 'oAuthRequest' is not null or undefined + assertParamExists('verifyOAuth', 'oAuthRequest', oAuthRequest) + const localVarPath = `/iam/v1/oauth/{provider}/verify` + .replace(`{${"provider"}}`, encodeURIComponent(String(provider))); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -10442,7 +10697,7 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf baseOptions = configuration.baseOptions; } - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; const localVarHeaderParameter = {} as any; const localVarQueryParameter = {} as any; @@ -10450,15 +10705,14 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) - if (token !== undefined) { - localVarQueryParameter['token'] = token; - } - + localVarHeaderParameter['Content-Type'] = 'application/json'; + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(oAuthRequest, localVarRequestOptions, configuration) return { url: toPathString(localVarUrlObj), @@ -10472,9 +10726,9 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf * @param {*} [options] Override http request option. * @throws {RequiredError} */ - verifyOAuth: async (authenticateOAuthRequest: AuthenticateOAuthRequest, options: AxiosRequestConfig = {}): Promise => { + verifyOAuthToken: async (authenticateOAuthRequest: AuthenticateOAuthRequest, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'authenticateOAuthRequest' is not null or undefined - assertParamExists('verifyOAuth', 'authenticateOAuthRequest', authenticateOAuthRequest) + assertParamExists('verifyOAuthToken', 'authenticateOAuthRequest', authenticateOAuthRequest) const localVarPath = `/iam/v1/oauth/verify`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -10491,10 +10745,6 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) - // authentication pk required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - localVarHeaderParameter['Content-Type'] = 'application/json'; @@ -10504,55 +10754,6 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(authenticateOAuthRequest, localVarRequestOptions, configuration) - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. - * @summary Retrieve player by token. - * @param {OAuthProvider} provider OAuth provider - * @param {OAuthRequest} oAuthRequest - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - verifyOAuthToken: async (provider: OAuthProvider, oAuthRequest: OAuthRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'provider' is not null or undefined - assertParamExists('verifyOAuthToken', 'provider', provider) - // verify required parameter 'oAuthRequest' is not null or undefined - assertParamExists('verifyOAuthToken', 'oAuthRequest', oAuthRequest) - const localVarPath = `/iam/v1/oauth/{provider}/verify` - .replace(`{${"provider"}}`, encodeURIComponent(String(provider))); - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication sk required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - // authentication pk required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(oAuthRequest, localVarRequestOptions, configuration) - return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -10599,7 +10800,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @deprecated * @throws {RequiredError} */ - async authorizeWithOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async authorizeWithOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.authorizeWithOAuthToken(provider, oAuthRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10714,14 +10915,16 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** - * Verifies the token generated by Openfort Auth. - * @summary Verify auth token. - * @param {string} token Specifies the auth token. + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - async verifyAuthToken(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.verifyAuthToken(token, options); + async verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuth(provider, oAuthRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -10731,21 +10934,8 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async verifyOAuth(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuth(authenticateOAuthRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. - * @summary Retrieve player by token. - * @param {OAuthProvider} provider OAuth provider - * @param {OAuthRequest} oAuthRequest - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - async verifyOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuthToken(provider, oAuthRequest, options); + async verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.verifyOAuthToken(authenticateOAuthRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -10787,7 +10977,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @deprecated * @throws {RequiredError} */ - authorizeWithOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: any): AxiosPromise { + authorizeWithOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: any): AxiosPromise { return localVarFp.authorizeWithOAuthToken(provider, oAuthRequest, options).then((request) => request(axios, basePath)); }, /** @@ -10891,14 +11081,16 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, return localVarFp.unlinkSIWE(sIWERequest, options).then((request) => request(axios, basePath)); }, /** - * Verifies the token generated by Openfort Auth. - * @summary Verify auth token. - * @param {string} token Specifies the auth token. + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - verifyAuthToken(token: string, options?: any): AxiosPromise { - return localVarFp.verifyAuthToken(token, options).then((request) => request(axios, basePath)); + verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: any): AxiosPromise { + return localVarFp.verifyOAuth(provider, oAuthRequest, options).then((request) => request(axios, basePath)); }, /** * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. @@ -10907,20 +11099,8 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @param {*} [options] Override http request option. * @throws {RequiredError} */ - verifyOAuth(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: any): AxiosPromise { - return localVarFp.verifyOAuth(authenticateOAuthRequest, options).then((request) => request(axios, basePath)); - }, - /** - * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. - * @summary Retrieve player by token. - * @param {OAuthProvider} provider OAuth provider - * @param {OAuthRequest} oAuthRequest - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - */ - verifyOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: any): AxiosPromise { - return localVarFp.verifyOAuthToken(provider, oAuthRequest, options).then((request) => request(axios, basePath)); + verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: any): AxiosPromise { + return localVarFp.verifyOAuthToken(authenticateOAuthRequest, options).then((request) => request(axios, basePath)); }, }; }; @@ -11091,15 +11271,17 @@ export class AuthenticationApi extends BaseAPI { } /** - * Verifies the token generated by Openfort Auth. - * @summary Verify auth token. - * @param {string} token Specifies the auth token. + * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. + * @summary Retrieve player by token. + * @param {OAuthProvider} provider OAuth provider + * @param {OAuthRequest} oAuthRequest * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof AuthenticationApi */ - public verifyAuthToken(token: string, options?: AxiosRequestConfig) { - return AuthenticationApiFp(this.configuration).verifyAuthToken(token, options).then((request) => request(this.axios, this.basePath)); + public verifyOAuth(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig) { + return AuthenticationApiFp(this.configuration).verifyOAuth(provider, oAuthRequest, options).then((request) => request(this.axios, this.basePath)); } /** @@ -11110,22 +11292,8 @@ export class AuthenticationApi extends BaseAPI { * @throws {RequiredError} * @memberof AuthenticationApi */ - public verifyOAuth(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig) { - return AuthenticationApiFp(this.configuration).verifyOAuth(authenticateOAuthRequest, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * The endpoint verifies the token generated by OAuth provider and retrieves a corresponding player. - * @summary Retrieve player by token. - * @param {OAuthProvider} provider OAuth provider - * @param {OAuthRequest} oAuthRequest - * @param {*} [options] Override http request option. - * @deprecated - * @throws {RequiredError} - * @memberof AuthenticationApi - */ - public verifyOAuthToken(provider: OAuthProvider, oAuthRequest: OAuthRequest, options?: AxiosRequestConfig) { - return AuthenticationApiFp(this.configuration).verifyOAuthToken(provider, oAuthRequest, options).then((request) => request(this.axios, this.basePath)); + public verifyOAuthToken(authenticateOAuthRequest: AuthenticateOAuthRequest, options?: AxiosRequestConfig) { + return AuthenticationApiFp(this.configuration).verifyOAuthToken(authenticateOAuthRequest, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/src/openfort.ts b/src/openfort.ts index 126662d..58e5b75 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -7,7 +7,7 @@ import { TransactionIntentsApi, } from "./generated"; import {ISigner, SignerType} from "./signer/signer"; -import {Auth, OpenfortAuth} from "./openfortAuth"; +import {Auth, InitAuthResponse, OpenfortAuth} from "./openfortAuth"; import {AuthTokenStorageKey, IStorage, PlayerIDStorageKey, RefreshTokenStorageKey} from "./storage/storage"; import {LocalStorage} from "./storage/local-storage"; import {SessionSigner} from "./signer/session.signer"; @@ -103,6 +103,16 @@ export default class Openfort { return result.accessToken; } + public async loginWithOAuth(provider: OAuthProvider): Promise { + return await this._openfortAuth.getAuthenticationURL(provider); + } + + public async getTokenAfterSocialLogin(provider: OAuthProvider, key: string): Promise { + const result = await this._openfortAuth.GetTokenAfterSocialLogin(provider, key); + this.storeCredentials(result); + return result.accessToken; + } + private storeCredentials(auth: Auth): void { this._storage.save(AuthTokenStorageKey, auth.accessToken); this._storage.save(RefreshTokenStorageKey, auth.refreshToken); diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index a3beb2a..787981c 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -7,6 +7,12 @@ export type Auth = { refreshToken: string; }; +export type InitAuthResponse = { + url: string; + key: string; +}; + + export class OpenfortAuth { private readonly _configuration: Configuration; private _oauthApi?: AuthenticationApi; @@ -36,6 +42,23 @@ export class OpenfortAuth { }; } + public async getAuthenticationURL(provider: OAuthProvider): Promise { + const result = await this._oauthApi.initOAuth({token: "", provider: provider}); + return { + url: result.data.url, + key: result.data.key, + } + } + + public async GetTokenAfterSocialLogin(provider: OAuthProvider, key: string): Promise { + const result = await this._oauthApi.authenticateOAuth({provider: provider, token: key}); + return { + player: result.data.player.id, + accessToken: result.data.token, + refreshToken: result.data.refreshToken, + }; + } + public async authorizeWithEmailPassword(email: string, password: string): Promise { const result = await this._oauthApi.loginEmailPassword({email, password}); return { From 122b7761f0bd2ffab70ab04d8447812bd2b3390f Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 13:02:18 +0100 Subject: [PATCH 11/13] fix: typo --- src/openfort.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openfort.ts b/src/openfort.ts index 58e5b75..b003d63 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -103,11 +103,11 @@ export default class Openfort { return result.accessToken; } - public async loginWithOAuth(provider: OAuthProvider): Promise { + public async initOAuth(provider: OAuthProvider): Promise { return await this._openfortAuth.getAuthenticationURL(provider); } - public async getTokenAfterSocialLogin(provider: OAuthProvider, key: string): Promise { + public async authenticateOAuth(provider: OAuthProvider, key: string): Promise { const result = await this._openfortAuth.GetTokenAfterSocialLogin(provider, key); this.storeCredentials(result); return result.accessToken; From 79b5391cdd598ffc178f6846483d85b574b577e2 Mon Sep 17 00:00:00 2001 From: gllm-dev Date: Mon, 11 Mar 2024 13:30:22 +0100 Subject: [PATCH 12/13] chore: logout --- src/clients/iframe-client.ts | 36 ++++++++++++++++++++++++++++++++--- src/openfort.ts | 7 +++++-- src/openfortAuth.ts | 4 ++++ src/signer/embedded.signer.ts | 13 +++++++------ src/signer/session.signer.ts | 4 +++- src/signer/signer.ts | 2 +- 6 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index b201936..d9d201a 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -164,7 +164,37 @@ export class IframeClient { }); } - dispose() { - document.body.removeChild(this._iframe); - } + async dispose(): Promise { + await this.waitForIframeLoad(); + + return new Promise((resolve, reject) => { + const handleMessage = (event: MessageEvent) => { + if (event.data.action === "loggedOut") { + if (event.data.success) { + document.body.removeChild(this._iframe); + resolve(); + } else { + reject(new Error(event.data.error || "Dispose failed")); + } + + window.removeEventListener("message", handleMessage); + } + }; + + window.addEventListener("message", handleMessage); + + setTimeout(() => { + if (this._iframe.contentWindow) { + this._iframe.contentWindow.postMessage( + { + action: "logout", + }, + "*", + ); + } else { + console.error("No iframe content window"); + } + }, 1000); + }); + }; } diff --git a/src/openfort.ts b/src/openfort.ts index b003d63..8145124 100644 --- a/src/openfort.ts +++ b/src/openfort.ts @@ -32,12 +32,15 @@ export default class Openfort { this._transactionsApi = new TransactionIntentsApi(configuration); } - public logout(): void { + public async logout(): Promise { + if (this.isAuthenticated()) { + await this._openfortAuth.logout(this._storage.get(RefreshTokenStorageKey)); + } this._storage.remove(AuthTokenStorageKey); this._storage.remove(RefreshTokenStorageKey); this._storage.remove(PlayerIDStorageKey); if (this._signer) { - this._signer.logout(); + await this._signer.logout(); } } diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index 787981c..019bf64 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -116,4 +116,8 @@ export class OpenfortAuth { } } } + + async logout(refreshToken: string) { + await this._oauthApi.logout({refreshToken}); + } } diff --git a/src/signer/embedded.signer.ts b/src/signer/embedded.signer.ts index 19b806b..08a473e 100644 --- a/src/signer/embedded.signer.ts +++ b/src/signer/embedded.signer.ts @@ -6,10 +6,9 @@ import {IRecovery} from "../recovery/recovery"; export class EmbeddedSigner implements ISigner { private _iframeClient: IframeClient; - private readonly _recoverySharePassword?: string; private _deviceID: string | null = null; private readonly _publishableKey: string; - private readonly _chainId: number; + private _chainId: number; private readonly _iframeURL: string | null; private readonly _storage: IStorage; private _recovery: IRecovery; @@ -22,8 +21,10 @@ export class EmbeddedSigner implements ISigner { this.configureIframeClient(); } - logout(): void { - this.dispose(); + async logout(): Promise { + await this.dispose(); + this._deviceID = null; + this._chainId = 0; } useCredentials(): boolean { return true; @@ -67,8 +68,8 @@ export class EmbeddedSigner implements ISigner { return await this._iframeClient.sign(message as string); } - public dispose(): void { - this._iframeClient.dispose(); + public async dispose(): Promise { + await this._iframeClient.dispose(); } public setRecovery(recovery: IRecovery): void { diff --git a/src/signer/session.signer.ts b/src/signer/session.signer.ts index fa22e5b..7e6d9a9 100644 --- a/src/signer/session.signer.ts +++ b/src/signer/session.signer.ts @@ -17,8 +17,10 @@ export class SessionSigner implements ISigner { }); } - logout(): void { + logout(): Promise { this._storage.remove(SessionKeyStorageKey); + this._sessionKey = null; + return Promise.resolve(); } loadKeys(): string { if (this._sessionKey !== null) { diff --git a/src/signer/signer.ts b/src/signer/signer.ts index 931f510..82153fa 100644 --- a/src/signer/signer.ts +++ b/src/signer/signer.ts @@ -7,7 +7,7 @@ export enum SignerType { export interface ISigner { sign(message: Bytes | string): Promise; - logout(): void; + logout(): Promise; useCredentials(): boolean; updateAuthentication(): void; getSingerType(): SignerType; From cc49cea2c8eca88fbd5a1566ba47f1c922d790c9 Mon Sep 17 00:00:00 2001 From: Jaume Alavedra Date: Mon, 11 Mar 2024 13:46:10 +0100 Subject: [PATCH 13/13] lint and format --- src/clients/iframe-client.ts | 2 +- src/openfortAuth.ts | 3 +-- src/storage/local-storage.ts | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/clients/iframe-client.ts b/src/clients/iframe-client.ts index d9d201a..c4e2af8 100644 --- a/src/clients/iframe-client.ts +++ b/src/clients/iframe-client.ts @@ -196,5 +196,5 @@ export class IframeClient { } }, 1000); }); - }; + } } diff --git a/src/openfortAuth.ts b/src/openfortAuth.ts index 019bf64..c966551 100644 --- a/src/openfortAuth.ts +++ b/src/openfortAuth.ts @@ -12,7 +12,6 @@ export type InitAuthResponse = { key: string; }; - export class OpenfortAuth { private readonly _configuration: Configuration; private _oauthApi?: AuthenticationApi; @@ -47,7 +46,7 @@ export class OpenfortAuth { return { url: result.data.url, key: result.data.key, - } + }; } public async GetTokenAfterSocialLogin(provider: OAuthProvider, key: string): Promise { diff --git a/src/storage/local-storage.ts b/src/storage/local-storage.ts index 7fa9eb2..605a609 100644 --- a/src/storage/local-storage.ts +++ b/src/storage/local-storage.ts @@ -1,8 +1,6 @@ import {IStorage} from "./storage"; export class LocalStorage implements IStorage { - constructor() {} - private validateLocalStorage(): void { if (!("localStorage" in global && !!global.localStorage)) { throw new Error("Local storage is not available");