Skip to content

Commit

Permalink
use /status to get time drift before sending any intents (#605)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrislav authored Nov 7, 2024
1 parent 2649315 commit 8a42ef2
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
53 changes: 52 additions & 1 deletion packages/waas/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import {
SendERC721Args,
SendTransactionsArgs,
SignedIntent,
SignMessageArgs
SignMessageArgs,
getTimeDrift,
updateTimeDrift
} from './intents'
import {
FeeOptionsResponse,
Expand Down Expand Up @@ -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<any>) {
const sessionId = await this.waas.getSessionId()
if (!sessionId) {
Expand Down Expand Up @@ -430,6 +443,8 @@ export class SequenceWaaS {
}

async initAuth(identity: Identity): Promise<Challenge> {
await this.updateTimeDrift()

if ('guest' in identity && identity.guest) {
return this.initGuestAuth()
} else if ('idToken' in identity) {
Expand Down Expand Up @@ -493,6 +508,8 @@ export class SequenceWaaS {
challenge: Challenge,
opts?: { sessionName?: string; forceCreateAccount?: boolean }
): Promise<SignInResponse> {
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.
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -594,6 +613,8 @@ export class SequenceWaaS {
}

async listSessions(): Promise<Sessions> {
await this.updateTimeDrift()

const sessionId = await this.waas.getSessionId()
if (!sessionId) {
throw new Error('session not open')
Expand All @@ -614,6 +635,8 @@ export class SequenceWaaS {
}

async validateSession(args?: ValidationArgs) {
await this.updateTimeDrift()

if (await this.isSessionValid()) {
return true
}
Expand All @@ -622,6 +645,8 @@ export class SequenceWaaS {
}

async finishValidateSession(challenge: string): Promise<boolean> {
await this.updateTimeDrift()

const intent = await this.waas.finishValidateSession(this.validationRequiredSalt, challenge)
const result = await this.sendIntent(intent)

Expand All @@ -634,6 +659,8 @@ export class SequenceWaaS {
}

async isSessionValid(): Promise<boolean> {
await this.updateTimeDrift()

const intent = await this.waas.getSession()
const result = await this.sendIntent(intent)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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<IntentResponseIdToken> {
await this.updateTimeDrift()

const intent = await this.waas.getIdToken({ nonce: args?.nonce })
const res = await this.sendIntent(intent)

Expand Down Expand Up @@ -737,6 +774,8 @@ export class SequenceWaaS {
}

async signMessage(args: WithSimpleNetwork<SignMessageArgs> & CommonAuthArgs): Promise<SignedMessageResponse> {
await this.updateTimeDrift()

const intent = await this.waas.signMessage(await this.useIdentifier(args))
return this.trySendIntent(args, intent, isSignedMessageResponse)
}
Expand Down Expand Up @@ -764,31 +803,43 @@ export class SequenceWaaS {
}

async sendTransaction(args: WithSimpleNetwork<SendTransactionsArgs> & CommonAuthArgs): Promise<MaySentTransactionResponse> {
await this.updateTimeDrift()

const intent = await this.waas.sendTransaction(await this.useIdentifier(args))
return this.trySendTransactionIntent(intent, args)
}

async sendERC20(args: WithSimpleNetwork<SendERC20Args> & CommonAuthArgs): Promise<MaySentTransactionResponse> {
await this.updateTimeDrift()

const intent = await this.waas.sendERC20(await this.useIdentifier(args))
return this.trySendTransactionIntent(intent, args)
}

async sendERC721(args: WithSimpleNetwork<SendERC721Args> & CommonAuthArgs): Promise<MaySentTransactionResponse> {
await this.updateTimeDrift()

const intent = await this.waas.sendERC721(await this.useIdentifier(args))
return this.trySendTransactionIntent(intent, args)
}

async sendERC1155(args: WithSimpleNetwork<SendERC1155Args> & CommonAuthArgs): Promise<MaySentTransactionResponse> {
await this.updateTimeDrift()

const intent = await this.waas.sendERC1155(await this.useIdentifier(args))
return this.trySendTransactionIntent(intent, args)
}

async callContract(args: WithSimpleNetwork<SendContractCallArgs> & CommonAuthArgs): Promise<MaySentTransactionResponse> {
await this.updateTimeDrift()

const intent = await this.waas.callContract(await this.useIdentifier(args))
return this.trySendTransactionIntent(intent, args)
}

async feeOptions(args: WithSimpleNetwork<SendTransactionsArgs> & CommonAuthArgs): Promise<FeeOptionsResponse> {
await this.updateTimeDrift()

const intent = await this.waas.feeOptions(await this.useIdentifier(args))
return this.trySendIntent(args, intent, isFeeOptionsResponse)
}
Expand Down
29 changes: 27 additions & 2 deletions packages/waas/src/intents/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,34 @@ export type SignedIntent<T> = Omit<RawIntent, 'data'> & { 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<T>(name: IntentName, lifespan: number, data: T): Intent<T> {
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,
Expand Down

0 comments on commit 8a42ef2

Please sign in to comment.