Skip to content

Commit

Permalink
guard: auth methods interface
Browse files Browse the repository at this point in the history
  • Loading branch information
attente committed Oct 10, 2023
1 parent ef539fb commit e5dd37e
Showing 1 changed file with 168 additions and 3 deletions.
171 changes: 168 additions & 3 deletions packages/guard/src/signer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { signers, Status } from '@0xsequence/signhub'
import { BytesLike, ethers } from 'ethers'
import { Guard } from './guard.gen'
import { Account } from '@0xsequence/account'
import { commons, universal } from '@0xsequence/core'
import { signers, Status } from '@0xsequence/signhub'
import { TypedData } from '@0xsequence/utils'
import { BytesLike, ethers, TypedDataDomain } from 'ethers'
import { AuthMethodsReturn, Guard } from './guard.gen'

const fetch = typeof global === 'object' ? global.fetch : window.fetch

Expand Down Expand Up @@ -62,6 +64,63 @@ export class GuardSigner implements signers.SapientSigner {
this.evaluateRequest(id, status.message, status, metadata)
}

async getAuthMethods(token: GetAuthMethodsToken): Promise<AuthMethod[]> {
let response: AuthMethodsReturn

if ('jwt' in token) {
response = await this.guard.authMethods({}, { Authorization: `BEARER ${token.jwt}` })
} else {
const signedToken = await signGetAuthMethodsToken(token)
response = await this.guard.authMethods({ request: { ...signedToken, timestamp: signedToken.timestamp.getTime() } })
}

return response.methods.map(parseAuthMethod)
}

async setPin(pin: string | undefined, token: UpdateAuthMethodToken): Promise<void> {
const signedToken = await signUpdateAuthMethodToken(token)

if (pin === undefined) {
await this.guard.resetPIN(
{ timestamp: signedToken.timestamp.getTime(), signature: signedToken.signature },
{ Authorization: `BEARER ${token.jwt}` }
)
} else {
await this.guard.setPIN(
{ pin, timestamp: signedToken.timestamp.getTime(), signature: signedToken.signature },
{ Authorization: `BEARER ${token.jwt}` }
)
}
}

resetPin(token: UpdateAuthMethodToken): Promise<void> {
return this.setPin(undefined, token)
}

async createTotp(token: UpdateAuthMethodToken): Promise<URL> {
const signedToken = await signUpdateAuthMethodToken(token)

const { uri } = await this.guard.createTOTP(
{ timestamp: signedToken.timestamp.getTime(), signature: signedToken.signature },
{ Authorization: `BEARER ${token.jwt}` }
)

return new URL(uri)
}

async commitTotp(token: string, jwt: string): Promise<void> {
await this.guard.commitTOTP({ token }, { Authorization: `BEARER ${jwt}` })
}

async resetTotp(token: UpdateAuthMethodToken): Promise<void> {
const signedToken = await signUpdateAuthMethodToken(token)

await this.guard.resetTOTP(
{ timestamp: signedToken.timestamp.getTime(), signature: signedToken.signature },
{ Authorization: `BEARER ${token.jwt}` }
)
}

private packMsgAndSig(address: string, msg: BytesLike, sig: BytesLike, chainId: ethers.BigNumberish): string {
return ethers.utils.defaultAbiCoder.encode(['address', 'uint256', 'bytes', 'bytes'], [address, chainId, msg, sig])
}
Expand Down Expand Up @@ -115,3 +174,109 @@ export class GuardSigner implements signers.SapientSigner {
return this.appendSuffix ? [3] : []
}
}

export enum AuthMethod {
Pin = 'PIN',
Totp = 'TOTP'
}

function parseAuthMethod(method: string): AuthMethod {
switch (method) {
case AuthMethod.Pin:
case AuthMethod.Totp:
return method
default:
throw new Error(`unknown auth method '${method}'`)
}
}

export type GetAuthMethodsToken =
| { jwt: string }
| {
wallet: string
timestamp: Date
signer: string
signature: string
}
| {
wallet: string
signer: ethers.Wallet
}

function isSignedGetAuthMethodsToken(
token: GetAuthMethodsToken
): token is { wallet: string; timestamp: Date; signer: string; signature: string } {
return 'signer' in token && typeof token.signer === 'string'
}

async function signGetAuthMethodsToken(
token: Exclude<GetAuthMethodsToken, { jwt: string }>
): Promise<{ wallet: string; timestamp: Date; signer: string; signature: string }> {
if (isSignedGetAuthMethodsToken(token)) {
return token
} else {
const timestamp = new Date()
const typedData = getTypedDataForGetAuthMethods(token.wallet, timestamp)

const signature = await token.signer._signTypedData(typedData.domain, typedData.types, typedData.message)

return { wallet: token.wallet, timestamp, signer: token.signer.address, signature }
}
}

export type UpdateAuthMethodToken = { jwt: string } & ({ timestamp: Date; signature: string } | { wallet: Account })

async function signUpdateAuthMethodToken(
token: UpdateAuthMethodToken
): Promise<{ jwt: string; timestamp: Date; signature: string }> {
if ('wallet' in token) {
const timestamp = new Date()
const typedData = getTypedDataForUpdateAuthMethod(timestamp)

const signature = await token.wallet.signTypedData(
typedData.domain,
typedData.types,
typedData.message,
typedData.domain.chainId ?? 1,
'eip6492'
)

return { jwt: token.jwt, timestamp, signature }
} else {
return token
}
}

export function getTypedDataForGetAuthMethods(wallet: string, timestamp: Date): TypedData {
return {
domain,
types: {
AuthMethods: [
{ name: 'wallet', type: 'address' },
{ name: 'timestamp', type: 'string' }
]
},
message: {
wallet: ethers.utils.getAddress(wallet),
timestamp: timestamp.toUTCString()
}
}
}

export function getTypedDataForUpdateAuthMethod(timestamp: Date): TypedData {
return {
domain,
types: {
AuthUpdate: [{ name: 'timestamp', type: 'string' }]
},
message: {
timestamp: timestamp.toUTCString()
}
}
}

const domain: TypedDataDomain = {
name: 'Sequence Guard',
version: '1',
chainId: 1
}

0 comments on commit e5dd37e

Please sign in to comment.