Skip to content

Commit

Permalink
Code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ivande committed Jun 4, 2024
1 parent 7c50145 commit 27f16a6
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 346 deletions.
6 changes: 3 additions & 3 deletions dev/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ app.get('/', (_, res) => {
res.redirect('/admin')
})

app.get('/admin/login', (_, res) => {
res.redirect('/admin/login-2fa')
})
app.get('/admin/login', (_, res) => {
res.redirect('/admin/login-2fa')
})

// parse application/json
app.use(express.json())
Expand Down
15 changes: 15 additions & 0 deletions src/endpoints/Endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import loginResponseEndpoint from './loginResponseEndpoint'
import loginChallengeEndpoint from './loginChallengeEndpoint'
import multiAuthLoginEndpoint from './multiAuthLoginEndpoint'
import multiAuthPreEndpoint from './multiAuthPreEndpoint'
import registerChallengeEndpoint from './registerChallengeEndpoint'
import registerResponseEndpoint from './registerResponseEndpoint'

export {
loginResponseEndpoint,
loginChallengeEndpoint,
multiAuthLoginEndpoint,
multiAuthPreEndpoint,
registerChallengeEndpoint,
registerResponseEndpoint,
}
42 changes: 42 additions & 0 deletions src/endpoints/loginChallengeEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Endpoint } from 'payload/config'
import { generateAuthenticationOptions } from '@simplewebauthn/server'
import { getUserByEmail, getUserPasskeys } from '../utils'
import pluginConfig from '../pluginConfig'

const loginChallengeEndpoint: Endpoint = {
path: '/multi-auth/auth/challenge',
method: 'post',
root: true,
handler: async (req, res) => {
if (!req.body || !req.body.email) {
return res.status(400).json({ message: 'Email missing' })
}

const user = await getUserByEmail(req.body.email, req.payload, pluginConfig.authCollectionSlug)
const userPasskeys = await getUserPasskeys(user, req.payload)

const options = await generateAuthenticationOptions({
rpID: 'localhost', // Use your actual domain in production
allowCredentials: userPasskeys.map((passkey: { credentialID: string }) => ({
id: passkey.credentialID,
type: 'public-key',
transports: ['usb', 'nfc', 'ble', 'internal'],
challenge: passkey.challenge,
})),
userVerification: 'preferred',
})

console.log(options)

await req.payload.update({
collection: pluginConfig.authCollectionSlug,
id: user.id,
data: { options } as any,
})
res.json(options)

return res
},
}

export default loginChallengeEndpoint
74 changes: 74 additions & 0 deletions src/endpoints/loginResponseEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Endpoint } from 'payload/config'
import { randomBytes } from 'crypto'
import { getUserByEmail, getUserPasskeys } from '../utils'
import pluginConfig from '../pluginConfig'

const loginResponseEndpoint: Endpoint = {
path: '/multi-auth/auth/verify',
method: 'post',
root: true,
handler: async (req, res) => {
const { body } = req

if (!body) {
return res.status(400).json({ message: 'Malformed request' });
}

const user = await getUserByEmail(body.email, req.payload, pluginConfig.authCollectionSlug)
const passkeys = await getUserPasskeys(user, req.payload)

const passkey = passkeys.find(
(existingPasskey: { credentialID: any }) => existingPasskey.credentialID === body.id,
)

if (!passkey) {
return res.status(401).json({ message: 'Invalid credentials' })
}

// let verification;
// try {
// verification = await verifyAuthenticationResponse({
// response: body,
// expectedChallenge: user.registrationOptions.challenge,
// expectedOrigin: 'http://localhost:3000',
// expectedRPID: 'localhost',
// authenticator: {
// credentialID: passkey.credentialID,
// credentialPublicKey: passkey.publicKey,
// counter: passkey.counter,
// transports: passkey.transports,
// },
// });
// } catch (error) {
// console.error(error);
// return res.status(400).send({ error: error.message });
// }

// const { verified } = verification;

const password = randomBytes(32).toString('hex')

// await req.payload.update({
// collection: pluginConfig.authCollectionSlug,
// id: user.id,
// data: {password} as any,
// })

const loginData = {
collection: pluginConfig.authCollectionSlug,
data: {
email: user.email,
password: 'Caconmi08!',
},
req,
}
console.log(loginData)
const result = await req.payload.login(loginData)

const data = { success: true, data: result }
console.log(data)
return res.json(data)
},
}

export default loginResponseEndpoint
48 changes: 48 additions & 0 deletions src/endpoints/multiAuthLoginEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Endpoint } from 'payload/config'
import { getUserByEmail } from '../utils'
import pluginConfig from '../pluginConfig'

const multiAuthLoginEndpoint: Endpoint = {
path: '/multi-auth/auth/login',
method: 'post',
root: true,
handler: async (req, res) => {
// secure this request
if (!req.body) {
return res.status(400).json({ message: 'Malformed request' })
}

const { email, password } = req.body
const user = await getUserByEmail(email, req.payload, pluginConfig.authCollectionSlug)

if (!user) {
return res.status(404).json({ message: 'User not found' })
}

const loginData = {
collection: pluginConfig.authCollectionSlug,
data: {
email,
password,
},
req,
}

let result
try {
result = await req.payload.login(loginData)
} catch (error: unknown) {
console.log(error.message)
return res.status(401).json({ message: error.message })
}

if (user.authMethod === 'password_2fa' && user.tokenValidation === false) {
return res.status(401).json({ message: 'Invalid token' })
}

const data = { success: true, data: result }
return res.json(data)
},
}

export default multiAuthLoginEndpoint
28 changes: 28 additions & 0 deletions src/endpoints/multiAuthPreEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Endpoint } from 'payload/config'
import { getUserByEmail } from '../utils'
import pluginConfig from '../pluginConfig'

const multiAuthPreEndpoint: Endpoint = {
path: '/multi-auth/auth/pre',
method: 'post',
root: true,
handler: async (req, res) => {
// secure this request
if (!req.body) {
return res.status(400).json({message: 'Malformed request'})
}

const { email } = req.body

const user = await getUserByEmail(email, req.payload, pluginConfig.authCollectionSlug)

if (!user) {
return res.status(404).json({ message: 'User not found' });
}

// return user preferred authentication method
return res.json({ authMethod: user.authMethod || 'password' })
},
}

export default multiAuthPreEndpoint
58 changes: 58 additions & 0 deletions src/endpoints/registerChallengeEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Endpoint } from 'payload/config'
import { generateRegistrationOptions } from '@simplewebauthn/server'
import { getUserByEmail, getUserPasskeys } from '../utils'
import pluginConfig from '../pluginConfig'

const registerChallengeEndpoint: Endpoint = {
path: '/multi-auth/register/challenge',
method: 'post',
root: true,
handler: async (req, res) => {
const body = req.body

if (!body.email) {
return res.status(400).json({ message: 'Email is required' })
}

try {
const user = await getUserByEmail(body.email, req.payload, pluginConfig.authCollectionSlug)

const userPasskeys = await getUserPasskeys(user, req.payload)
const rpName = pluginConfig.rpName
const rpID = pluginConfig.rpID

// Generate registration options
const options = await generateRegistrationOptions({
rpName,
rpID,
userName: user.email,
attestationType: 'none',
excludeCredentials: userPasskeys.map((passkey: { credentialID: any; transports: any; }) => ({
id: passkey.credentialID,
})),
authenticatorSelection: {
residentKey: 'preferred',
userVerification: 'preferred',
authenticatorAttachment: 'platform',
},
})

// save the options to the user
await req.payload.update({
collection: pluginConfig.authCollectionSlug,
id: user.id,
data: {
registrationOptions: options,
},
})

// Send the options back to the client
res.json(options)
} catch (error: unknown) {
console.error('Error processing request:', error)
res.status(500).send('Internal Server Error')
}
},
}

export default registerChallengeEndpoint
59 changes: 59 additions & 0 deletions src/endpoints/registerResponseEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Endpoint } from 'payload/config'
import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/types'
import { verifyRegistrationResponse } from '@simplewebauthn/server'
import type Passkey from '../types/Passkey'
import pluginConfig from '../pluginConfig'
import {getUserByEmail} from "../utils";

const registerResponseEndpoint: Endpoint = {
path: '/multi-auth/register/verify',
method: 'post',
root: true,
handler: async (req, res) => {
const body = req.body
const user = await getUserByEmail(body.email, req.payload, pluginConfig.authCollectionSlug)

console.log("verify")
// @ts-ignore
const currentOptions: PublicKeyCredentialCreationOptionsJSON = user.registrationOptions

let verification
try {
verification = await verifyRegistrationResponse({
response: body,
expectedChallenge: currentOptions.challenge,
expectedOrigin: 'http://localhost:3000',
expectedRPID: 'localhost',
})
} catch (error: unknown) {
console.error(error)
return res.status(400).send({ error: error.message })
}

const { verified } = verification
const { registrationInfo } = verification
const { credentialID, credentialPublicKey, counter, credentialDeviceType, credentialBackedUp } =
registrationInfo

const newPasskey: Passkey = {
user: user.id,
webAuthnUserID: currentOptions.user.id,
credentialID: credentialID,
publicKey: credentialPublicKey,
counter,
deviceType: credentialDeviceType,
backedUp: credentialBackedUp,
transports: body.response.transports.join(','),
}

// Save the new passkey to the database
const payload = await req.payload.create({
collection: 'authenticators',
data: newPasskey,
})

return res.json({ verified })
},
}

export default registerResponseEndpoint
Loading

0 comments on commit 27f16a6

Please sign in to comment.