diff --git a/package.json b/package.json index f9a47fff6..f96e996b2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "version": "1.0.0", "license": "AGPL-3.0-only", "devDependencies": { - "@simplewebauthn/types": "9.0.1", "@types/autocannon": "^7.12.6", "@types/chai": "^5.0.1", "@types/cross-spawn": "^6.0.6", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 1db516935..674f43e5e 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -20,7 +20,7 @@ "@hydrooj/common": "workspace:^", "@hydrooj/framework": "workspace:^", "@hydrooj/utils": "workspace:^", - "@simplewebauthn/server": "9.0.3", + "@simplewebauthn/server": "13.1.1", "@undefined-moe/require-resolve-hook": "^1.1.0", "@yarnpkg/libzip": "^3.1.0", "adm-zip": "0.5.14", diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index dc180c74a..55a6fd691 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -1,5 +1,6 @@ import path from 'path'; import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server'; +import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers'; import yaml from 'js-yaml'; import { pick } from 'lodash'; import { Binary, ObjectId } from 'mongodb'; @@ -288,12 +289,12 @@ class HomeSecurityHandler extends Handler { const options = await generateRegistrationOptions({ rpName: system.get('server.name'), rpID: this.getAuthnHost(), - userID: this.user._id.toString(), + userID: isoUint8Array.fromUTF8String(this.user._id.toString()), userDisplayName: this.user.uname, userName: `${this.user.uname}(${this.user.mail})`, attestationType: 'direct', excludeCredentials: this.user._authenticators.map((c) => ({ - id: c.credentialID.buffer, + id: isoBase64URL.fromBuffer(c.credentialID.buffer), type: 'public-key', })), authenticatorSelection: { @@ -316,12 +317,13 @@ class HomeSecurityHandler extends Handler { }).catch(() => { throw new ValidationError('verify'); }); if (!verification.verified) throw new ValidationError('verify'); const info = verification.registrationInfo; - const id = Buffer.from(info.credentialID); + const id = isoBase64URL.toBuffer(info.credential.id); if (this.user._authenticators.find((c) => c.credentialID.buffer.toString() === id.toString())) throw new ValidationError('authenticator'); this.user._authenticators.push({ ...info, + counter: info.credential.counter, credentialID: new Binary(id), - credentialPublicKey: new Binary(Buffer.from(info.credentialPublicKey)), + credentialPublicKey: new Binary(Buffer.from(info.credential.publicKey)), attestationObject: new Binary(Buffer.from(info.attestationObject)), name, regat: Date.now(), diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index cebb40d91..9776cc879 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -1,4 +1,5 @@ import { generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server'; +import { isoBase64URL } from '@simplewebauthn/server/helpers'; import moment from 'moment-timezone'; import type { Binary } from 'mongodb'; import type { Context } from '../context'; @@ -144,8 +145,7 @@ class UserWebauthnHandler extends Handler { if (!udoc.authn) throw new AuthOperationError('authn', 'disabled'); const options = await generateAuthenticationOptions({ allowCredentials: udoc._authenticators.map((authenticator) => ({ - id: authenticator.credentialID.buffer, - type: 'public-key', + id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer), })), rpID: this.getAuthnHost(), userVerification: 'preferred', @@ -169,10 +169,10 @@ class UserWebauthnHandler extends Handler { expectedChallenge: challenge, expectedOrigin: this.request.headers.origin, expectedRPID: this.getAuthnHost(), - authenticator: { + credential: { ...authenticator, - credentialID: authenticator.credentialID.buffer, - credentialPublicKey: authenticator.credentialPublicKey.buffer, + id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer), + publicKey: authenticator.credentialPublicKey.buffer, }, }).catch(() => null); if (!verification?.verified) throw new ValidationError('authenticator'); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 2edb4803b..71cebc8cf 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -1,6 +1,5 @@ +import type { AttestationFormat, CredentialDeviceType, } from '@simplewebauthn/server'; import type { AuthenticationExtensionsAuthenticatorOutputs } from '@simplewebauthn/server/esm/helpers/decodeAuthenticatorExtensions'; -import type { AttestationFormat } from '@simplewebauthn/server/helpers'; -import { CredentialDeviceType } from '@simplewebauthn/types'; import type fs from 'fs'; import type { Dictionary, NumericDictionary } from 'lodash'; import type { Binary, FindCursor, ObjectId } from 'mongodb'; diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index de315081c..cfb4226b5 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -127,18 +127,12 @@ export class User { : doc.owner === this._id || (doc.maintainer || []).includes(this._id); } - hasPerm(...perm: bigint[]) { - for (const i in perm) { - if ((this.perm & this.scope & perm[i]) === perm[i]) return true; - } - return false; + hasPerm(...perms: bigint[]) { + return perms.some((perm) => (this.perm & this.scope & perm) === perm); } - hasPriv(...priv: number[]) { - for (const i in priv) { - if ((this.priv & priv[i]) === priv[i]) return true; - } - return false; + hasPriv(...privs: number[]) { + return privs.some((priv) => (this.priv & priv) === priv); } async checkPassword(password: string) { diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 60add35b6..744e9bcc5 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -27,7 +27,7 @@ "@hydrooj/utils": "workspace:^", "@sentry/browser": "^8.54.0", "@sentry/webpack-plugin": "^3.1.2", - "@simplewebauthn/browser": "9.0.1", + "@simplewebauthn/browser": "13.1.0", "@svgr/webpack": "^8.1.0", "@types/jquery": "^3.5.32", "@types/jquery.transit": "^0.9.33", diff --git a/packages/ui-default/pages/home_security.page.ts b/packages/ui-default/pages/home_security.page.ts index 972d17315..53a4b61a1 100644 --- a/packages/ui-default/pages/home_security.page.ts +++ b/packages/ui-default/pages/home_security.page.ts @@ -68,7 +68,7 @@ async function enableAuthn(type: string) { let credential; try { console.log(authnInfo); - credential = await startRegistration(authnInfo.authOptions); + credential = await startRegistration({ optionsJSON: authnInfo.authOptions }); } catch (err) { Notification.error(i18n('Failed to get credential: {0}', err)); return; diff --git a/packages/ui-default/pages/user_verify.page.ts b/packages/ui-default/pages/user_verify.page.ts index bb5e559ef..767a99a70 100644 --- a/packages/ui-default/pages/user_verify.page.ts +++ b/packages/ui-default/pages/user_verify.page.ts @@ -18,7 +18,7 @@ async function verifywebauthn($form) { return null; } Notification.info(i18n('Please follow the instructions on your device to complete the verification.')); - const result = await startAuthentication(authnInfo.authOptions) + const result = await startAuthentication({ optionsJSON: authnInfo.authOptions }) .catch((e) => { Notification.error(i18n('Failed to get credential: {0}', e)); return null;