Skip to content

Commit

Permalink
feat: auth.register auto logins
Browse files Browse the repository at this point in the history
  • Loading branch information
hugomrdias committed May 18, 2023
1 parent d7cd1d2 commit 1a652a2
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 78 deletions.
1 change: 1 addition & 0 deletions packages/odd-passkeys/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"dependencies": {
"@noble/ed25519": "^2.0.0",
"@oddjs/odd": "^0.37.1",
"idb-keyval": "^6.2.1",
"iso-base": "^0.1.5",
"iso-passkeys": "^0.1.5"
},
Expand Down
177 changes: 99 additions & 78 deletions packages/odd-passkeys/src/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { utf8 } from 'iso-base/utf8'
import { u8 } from 'iso-base/utils'
import { credentialsCreate, credentialsGet } from 'iso-passkeys'
import * as Crypto from './crypto.js'
import { get, set } from 'idb-keyval'

/**
* Create an encryption key from passkey material
Expand Down Expand Up @@ -66,7 +67,7 @@ function rpFromConfig(config) {
* @param {import('@oddjs/odd').Storage.Implementation} storage
* @param {import('@oddjs/odd').Configuration} config
*/
export async function registerPasskey(username, storage, config) {
async function registerPasskey(username, storage, config) {
const rp = rpFromConfig(config)
const credential = await credentialsCreate({
publicKey: {
Expand Down Expand Up @@ -102,7 +103,7 @@ export async function registerPasskey(username, storage, config) {
throw new Error('PRF not supported.')
}

await storage.setItem('passkey/credentials', [credential])
await set('passkeys', [credential])
return credential
}

Expand All @@ -116,7 +117,7 @@ export async function registerPasskey(username, storage, config) {
async function getPasskey(config, storage, mediation) {
const rp = rpFromConfig(config)

const credentials = await storage.getItem('passkey/credentials')
const credentials = await get('passkeys')

const assertion = await credentialsGet({
mediation,
Expand Down Expand Up @@ -166,6 +167,85 @@ async function getPasskey(config, storage, mediation) {
}
}

/**
* Login
*
* @param {import('@oddjs/odd/components/auth/implementation/base').Dependencies & {config: import('@oddjs/odd').Configuration;}} deps
* @param {Awaited<ReturnType<getPasskey>>} [passkey]
* @param {string} [username]
*/
export async function login(deps, passkey, username) {
if (!passkey) {
passkey = await getPasskey(
deps.config,
deps.storage,
username ? undefined : 'conditional'
)
}

const { privateKey, publicKey, userHandle } = passkey

if (!username && !userHandle) {
throw new Error('Username is required')
}

const crypto = await Crypto.implementation(deps.config, publicKey, privateKey)

// Create an account UCAN
const accountUcan = await odd.ucan.build({
dependencies: {
crypto,
},
potency: 'APPEND',
resource: '*',
lifetimeInSeconds: 60 * 60 * 24 * 30 * 12 * 1000, // a lot of time

audience: await odd.did.ucan(deps.crypto),
issuer: publicKeyToDid(crypto, publicKey, 'ed25519'),
})

// Save account UCAN
await deps.storage.setItem(
deps.storage.KEYS.ACCOUNT_UCAN,
odd.ucan.encode(accountUcan)
)

await Session.provide(deps.storage, {
type: 'Passkey',
// @ts-ignore
username: username || userHandle,
})

return passkey
}

/**
* Register
*
* @param {import('@oddjs/odd/components/auth/implementation/base').Dependencies & {config: import('@oddjs/odd').Configuration;}} deps
* @param {string} username
*/
export async function register(deps, username) {
await registerPasskey(username, deps.storage, deps.config)

const passkey = await getPasskey(deps.config, deps.storage)

const registeAuth = odd.defaultAuthComponent({
crypto: await Crypto.implementation(
deps.config,
passkey.publicKey,
passkey.privateKey
),
reference: deps.reference,
storage: deps.storage,
})
const { success } = await registeAuth.register({ username })

if (success) {
return await login(deps, passkey, username)
}
}

/**
* Odd auth implementation
*
Expand All @@ -182,27 +262,25 @@ export function implementation(deps) {
isUsernameAvailable: defaultAuth.isUsernameAvailable,
isUsernameValid: defaultAuth.isUsernameValid,
linkDevice: defaultAuth.linkDevice,
register: async ({ username, email }) => {
await registerPasskey(username, deps.storage, deps.config)
register: async ({ username }) => {
if (!username) {
await login(deps, undefined, username)
return { success: true }
}

const { privateKey, publicKey } = await getPasskey(
deps.config,
deps.storage
)
const isValid = await defaultAuth.isUsernameValid(username)

const registeAuth = odd.defaultAuthComponent({
crypto: await Crypto.implementation(deps.config, publicKey, privateKey),
reference: deps.reference,
storage: deps.storage,
})
const { success } = await registeAuth.register({ username, email })
if (success) {
Session.provide(deps.storage, {
type: 'Passkey',
username,
})
return { success }
if (isValid) {
const isAvailable = await defaultAuth.isUsernameAvailable(username)
if (isAvailable) {
const passkey = await register(deps, username)
return passkey ? { success: true } : { success: false }
} else {
await login(deps, undefined, username)
return { success: true }
}
}

return { success: false }
},
session: async (components, username, config, eventEmitters) => {
Expand All @@ -218,60 +296,3 @@ export function implementation(deps) {
},
}
}

/**
* @param {import('@oddjs/odd').Program} program
* @param {string} [username]
*/
export async function login(program, username) {
if (program.session) {
return program.session
}

const { privateKey, publicKey, userHandle } = await getPasskey(
program.configuration,
program.components.storage,
username ? undefined : 'conditional'
)

if (!username && !userHandle) {
throw new Error('Username is required')
}

const crypto = await Crypto.implementation(
program.configuration,
publicKey,
privateKey
)

// Create an account UCAN
const accountUcan = await odd.ucan.build({
dependencies: {
crypto: await Crypto.implementation(
program.configuration,
publicKey,
privateKey
),
},
potency: 'APPEND',
resource: '*',
lifetimeInSeconds: 60 * 60 * 24 * 30 * 12 * 1000, // a lot of time

audience: await odd.did.ucan(program.components.crypto),
issuer: publicKeyToDid(crypto, publicKey, 'ed25519'),
})

// Save account UCAN
await program.components.storage.setItem(
program.components.storage.KEYS.ACCOUNT_UCAN,
odd.ucan.encode(accountUcan)
)

await Session.provide(program.components.storage, {
type: program.auth.implementation.type,
// @ts-ignore
username: username || userHandle,
})

return program.auth.session()
}

0 comments on commit 1a652a2

Please sign in to comment.