From 8a42ef2eae3aafa97af2aef95c9804602f03ce5c Mon Sep 17 00:00:00 2001 From: Patryk Kalinowski Date: Thu, 7 Nov 2024 16:54:00 +0100 Subject: [PATCH] use /status to get time drift before sending any intents (#605) --- packages/waas/src/auth.ts | 53 ++++++++++++++++++++++++++++++- packages/waas/src/intents/base.ts | 29 +++++++++++++++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/waas/src/auth.ts b/packages/waas/src/auth.ts index b5424e563..5ff4b606b 100644 --- a/packages/waas/src/auth.ts +++ b/packages/waas/src/auth.ts @@ -16,7 +16,9 @@ import { SendERC721Args, SendTransactionsArgs, SignedIntent, - SignMessageArgs + SignMessageArgs, + getTimeDrift, + updateTimeDrift } from './intents' import { FeeOptionsResponse, @@ -334,6 +336,17 @@ export class SequenceWaaS { } } + private async updateTimeDrift() { + if (getTimeDrift() === undefined) { + const res = await fetch(`${this.config.rpcServer}/status`) + const date = res.headers.get('Date') + if (!date) { + throw new Error('failed to get Date header value from /status') + } + updateTimeDrift(new Date(date)) + } + } + private async sendIntent(intent: SignedIntent) { const sessionId = await this.waas.getSessionId() if (!sessionId) { @@ -430,6 +443,8 @@ export class SequenceWaaS { } async initAuth(identity: Identity): Promise { + await this.updateTimeDrift() + if ('guest' in identity && identity.guest) { return this.initGuestAuth() } else if ('idToken' in identity) { @@ -493,6 +508,8 @@ export class SequenceWaaS { challenge: Challenge, opts?: { sessionName?: string; forceCreateAccount?: boolean } ): Promise { + await this.updateTimeDrift() + // initAuth can start while user is already signed in and continue with linkAccount method, // but it can't be used to completeAuth while user is already signed in. In this // case we should throw an error. @@ -559,6 +576,8 @@ export class SequenceWaaS { } async dropSession({ sessionId, strict }: { sessionId?: string; strict?: boolean } = {}) { + await this.updateTimeDrift() + const thisSessionId = await this.waas.getSessionId() if (!thisSessionId) { throw new Error('session not open') @@ -594,6 +613,8 @@ export class SequenceWaaS { } async listSessions(): Promise { + await this.updateTimeDrift() + const sessionId = await this.waas.getSessionId() if (!sessionId) { throw new Error('session not open') @@ -614,6 +635,8 @@ export class SequenceWaaS { } async validateSession(args?: ValidationArgs) { + await this.updateTimeDrift() + if (await this.isSessionValid()) { return true } @@ -622,6 +645,8 @@ export class SequenceWaaS { } async finishValidateSession(challenge: string): Promise { + await this.updateTimeDrift() + const intent = await this.waas.finishValidateSession(this.validationRequiredSalt, challenge) const result = await this.sendIntent(intent) @@ -634,6 +659,8 @@ export class SequenceWaaS { } async isSessionValid(): Promise { + await this.updateTimeDrift() + const intent = await this.waas.getSession() const result = await this.sendIntent(intent) @@ -659,11 +686,15 @@ export class SequenceWaaS { } async sessionAuthProof({ nonce, network, validation }: { nonce?: string; network?: string; validation?: ValidationArgs }) { + await this.updateTimeDrift() + const intent = await this.waas.sessionAuthProof({ nonce, network }) return await this.trySendIntent({ validation }, intent, isSessionAuthProofResponse) } async listAccounts() { + await this.updateTimeDrift() + const intent = await this.waas.listAccounts() const res = await this.sendIntent(intent) @@ -675,6 +706,8 @@ export class SequenceWaaS { } async linkAccount(challenge: Challenge) { + await this.updateTimeDrift() + const intent = await this.waas.linkAccount(challenge.getIntentParams()) const res = await this.sendIntent(intent) @@ -686,11 +719,15 @@ export class SequenceWaaS { } async removeAccount(accountId: string) { + await this.updateTimeDrift() + const intent = await this.waas.removeAccount({ accountId }) await this.sendIntent(intent) } async getIdToken(args?: { nonce?: string }): Promise { + await this.updateTimeDrift() + const intent = await this.waas.getIdToken({ nonce: args?.nonce }) const res = await this.sendIntent(intent) @@ -737,6 +774,8 @@ export class SequenceWaaS { } async signMessage(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.signMessage(await this.useIdentifier(args)) return this.trySendIntent(args, intent, isSignedMessageResponse) } @@ -764,31 +803,43 @@ export class SequenceWaaS { } async sendTransaction(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.sendTransaction(await this.useIdentifier(args)) return this.trySendTransactionIntent(intent, args) } async sendERC20(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.sendERC20(await this.useIdentifier(args)) return this.trySendTransactionIntent(intent, args) } async sendERC721(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.sendERC721(await this.useIdentifier(args)) return this.trySendTransactionIntent(intent, args) } async sendERC1155(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.sendERC1155(await this.useIdentifier(args)) return this.trySendTransactionIntent(intent, args) } async callContract(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.callContract(await this.useIdentifier(args)) return this.trySendTransactionIntent(intent, args) } async feeOptions(args: WithSimpleNetwork & CommonAuthArgs): Promise { + await this.updateTimeDrift() + const intent = await this.waas.feeOptions(await this.useIdentifier(args)) return this.trySendIntent(args, intent, isFeeOptionsResponse) } diff --git a/packages/waas/src/intents/base.ts b/packages/waas/src/intents/base.ts index d13141ca8..99f3df9eb 100644 --- a/packages/waas/src/intents/base.ts +++ b/packages/waas/src/intents/base.ts @@ -11,9 +11,34 @@ export type SignedIntent = Omit & { data: T } const INTENTS_VERSION = 1 const VERSION = `${INTENTS_VERSION} (Web ${PACKAGE_VERSION})` +let timeDrift: number | undefined +const timeDriftKey = '@sequence.timeDrift' + +function isSessionStorageAvailable() { + return typeof window === 'object' && typeof window.sessionStorage === 'object' +} + +export function getTimeDrift() { + if (isSessionStorageAvailable()) { + const drift = window.sessionStorage.getItem(timeDriftKey) + if (drift) { + return parseInt(drift, 10) + } + } + return timeDrift +} + +export function updateTimeDrift(serverTime: Date) { + timeDrift = (Date.now() - serverTime.getTime()) / 1000 + if (isSessionStorageAvailable()) { + window.sessionStorage.setItem(timeDriftKey, timeDrift.toString(10)) + } +} + export function makeIntent(name: IntentName, lifespan: number, data: T): Intent { - const issuedAt = Math.floor(Date.now() / 1000) - const expiresAt = issuedAt + lifespan + const drift = Math.abs(Math.floor(getTimeDrift() || 0)) + const issuedAt = Math.floor(Date.now() / 1000 - drift) + const expiresAt = issuedAt + lifespan + 2*drift return { version: VERSION, issuedAt,