diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-12 02:02:25 +0900 |
| commit | 0e4a111f81cceed275d9bec2695f6e401fb654d8 (patch) | |
| tree | 40874799472fa07416f17b50a398ac33b7771905 /src/server/api/endpoints/i/2fa | |
| parent | update deps (diff) | |
| download | misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.gz misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.tar.bz2 misskey-0e4a111f81cceed275d9bec2695f6e401fb654d8.zip | |
refactoring
Resolve #7779
Diffstat (limited to 'src/server/api/endpoints/i/2fa')
| -rw-r--r-- | src/server/api/endpoints/i/2fa/done.ts | 41 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/key-done.ts | 150 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/password-less.ts | 21 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/register-key.ts | 59 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/register.ts | 54 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/remove-key.ts | 45 | ||||
| -rw-r--r-- | src/server/api/endpoints/i/2fa/unregister.ts | 32 |
7 files changed, 0 insertions, 402 deletions
diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts deleted file mode 100644 index 2bd2128cce..0000000000 --- a/src/server/api/endpoints/i/2fa/done.ts +++ /dev/null @@ -1,41 +0,0 @@ -import $ from 'cafy'; -import * as speakeasy from 'speakeasy'; -import define from '../../../define'; -import { UserProfiles } from '@/models/index'; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - token: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - const token = ps.token.replace(/\s/g, ''); - - const profile = await UserProfiles.findOneOrFail(user.id); - - if (profile.twoFactorTempSecret == null) { - throw new Error('二段階認証の設定が開始されていません'); - } - - const verified = (speakeasy as any).totp.verify({ - secret: profile.twoFactorTempSecret, - encoding: 'base32', - token: token - }); - - if (!verified) { - throw new Error('not verified'); - } - - await UserProfiles.update(user.id, { - twoFactorSecret: profile.twoFactorTempSecret, - twoFactorEnabled: true - }); -}); diff --git a/src/server/api/endpoints/i/2fa/key-done.ts b/src/server/api/endpoints/i/2fa/key-done.ts deleted file mode 100644 index b4d3af235a..0000000000 --- a/src/server/api/endpoints/i/2fa/key-done.ts +++ /dev/null @@ -1,150 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import { promisify } from 'util'; -import * as cbor from 'cbor'; -import define from '../../../define'; -import { - UserProfiles, - UserSecurityKeys, - AttestationChallenges, - Users -} from '@/models/index'; -import config from '@/config/index'; -import { procedures, hash } from '../../../2fa'; -import { publishMainStream } from '@/services/stream'; - -const cborDecodeFirst = promisify(cbor.decodeFirst) as any; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - clientDataJSON: { - validator: $.str - }, - attestationObject: { - validator: $.str - }, - password: { - validator: $.str - }, - challengeId: { - validator: $.str - }, - name: { - validator: $.str - } - } -}; - -const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); - -export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } - - const clientData = JSON.parse(ps.clientDataJSON); - - if (clientData.type != 'webauthn.create') { - throw new Error('not a creation attestation'); - } - if (clientData.origin != config.scheme + '://' + config.host) { - throw new Error('origin mismatch'); - } - - const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); - - const attestation = await cborDecodeFirst(ps.attestationObject); - - const rpIdHash = attestation.authData.slice(0, 32); - if (!rpIdHashReal.equals(rpIdHash)) { - throw new Error('rpIdHash mismatch'); - } - - const flags = attestation.authData[32]; - - // tslint:disable-next-line:no-bitwise - if (!(flags & 1)) { - throw new Error('user not present'); - } - - const authData = Buffer.from(attestation.authData); - const credentialIdLength = authData.readUInt16BE(53); - const credentialId = authData.slice(55, 55 + credentialIdLength); - const publicKeyData = authData.slice(55 + credentialIdLength); - const publicKey: Map<number, any> = await cborDecodeFirst(publicKeyData); - if (publicKey.get(3) != -7) { - throw new Error('alg mismatch'); - } - - if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); - } - - const verificationData = (procedures as any)[attestation.fmt].verify({ - attStmt: attestation.attStmt, - authenticatorData: authData, - clientDataHash: clientDataJSONHash, - credentialId, - publicKey, - rpIdHash - }); - if (!verificationData.valid) throw new Error('signature invalid'); - - const attestationChallenge = await AttestationChallenges.findOne({ - userId: user.id, - id: ps.challengeId, - registrationChallenge: true, - challenge: hash(clientData.challenge).toString('hex') - }); - - if (!attestationChallenge) { - throw new Error('non-existent challenge'); - } - - await AttestationChallenges.delete({ - userId: user.id, - id: ps.challengeId - }); - - // Expired challenge (> 5min old) - if ( - new Date().getTime() - attestationChallenge.createdAt.getTime() >= - 5 * 60 * 1000 - ) { - throw new Error('expired challenge'); - } - - const credentialIdString = credentialId.toString('hex'); - - await UserSecurityKeys.save({ - userId: user.id, - id: credentialIdString, - lastUsed: new Date(), - name: ps.name, - publicKey: verificationData.publicKey.toString('hex') - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true - })); - - return { - id: credentialIdString, - name: ps.name - }; -}); diff --git a/src/server/api/endpoints/i/2fa/password-less.ts b/src/server/api/endpoints/i/2fa/password-less.ts deleted file mode 100644 index 064828b638..0000000000 --- a/src/server/api/endpoints/i/2fa/password-less.ts +++ /dev/null @@ -1,21 +0,0 @@ -import $ from 'cafy'; -import define from '../../../define'; -import { UserProfiles } from '@/models/index'; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - value: { - validator: $.boolean - } - } -}; - -export default define(meta, async (ps, user) => { - await UserProfiles.update(user.id, { - usePasswordLessLogin: ps.value - }); -}); diff --git a/src/server/api/endpoints/i/2fa/register-key.ts b/src/server/api/endpoints/i/2fa/register-key.ts deleted file mode 100644 index 1b385a10ee..0000000000 --- a/src/server/api/endpoints/i/2fa/register-key.ts +++ /dev/null @@ -1,59 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import define from '../../../define'; -import { UserProfiles, AttestationChallenges } from '@/models/index'; -import { promisify } from 'util'; -import * as crypto from 'crypto'; -import { genId } from '@/misc/gen-id'; -import { hash } from '../../../2fa'; - -const randomBytes = promisify(crypto.randomBytes); - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - password: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - if (!profile.twoFactorEnabled) { - throw new Error('2fa not enabled'); - } - - // 32 byte challenge - const entropy = await randomBytes(32); - const challenge = entropy.toString('base64') - .replace(/=/g, '') - .replace(/\+/g, '-') - .replace(/\//g, '_'); - - const challengeId = genId(); - - await AttestationChallenges.save({ - userId: user.id, - id: challengeId, - challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), - createdAt: new Date(), - registrationChallenge: true - }); - - return { - challengeId, - challenge - }; -}); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts deleted file mode 100644 index b03b98188a..0000000000 --- a/src/server/api/endpoints/i/2fa/register.ts +++ /dev/null @@ -1,54 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import * as speakeasy from 'speakeasy'; -import * as QRCode from 'qrcode'; -import config from '@/config/index'; -import define from '../../../define'; -import { UserProfiles } from '@/models/index'; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - password: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - // Generate user's secret key - const secret = speakeasy.generateSecret({ - length: 32 - }); - - await UserProfiles.update(user.id, { - twoFactorTempSecret: secret.base32 - }); - - // Get the data URL of the authenticator URL - const dataUrl = await QRCode.toDataURL(speakeasy.otpauthURL({ - secret: secret.base32, - encoding: 'base32', - label: user.username, - issuer: config.host - })); - - return { - qr: dataUrl, - secret: secret.base32, - label: user.username, - issuer: config.host - }; -}); diff --git a/src/server/api/endpoints/i/2fa/remove-key.ts b/src/server/api/endpoints/i/2fa/remove-key.ts deleted file mode 100644 index dea56301ab..0000000000 --- a/src/server/api/endpoints/i/2fa/remove-key.ts +++ /dev/null @@ -1,45 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import define from '../../../define'; -import { UserProfiles, UserSecurityKeys, Users } from '@/models/index'; -import { publishMainStream } from '@/services/stream'; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - password: { - validator: $.str - }, - credentialId: { - validator: $.str - }, - } -}; - -export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - // Make sure we only delete the user's own creds - await UserSecurityKeys.delete({ - userId: user.id, - id: ps.credentialId - }); - - // Publish meUpdated event - publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { - detail: true, - includeSecrets: true - })); - - return {}; -}); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts deleted file mode 100644 index af53033daa..0000000000 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ /dev/null @@ -1,32 +0,0 @@ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import define from '../../../define'; -import { UserProfiles } from '@/models/index'; - -export const meta = { - requireCredential: true as const, - - secure: true, - - params: { - password: { - validator: $.str - } - } -}; - -export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOneOrFail(user.id); - - // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); - - if (!same) { - throw new Error('incorrect password'); - } - - await UserProfiles.update(user.id, { - twoFactorSecret: null, - twoFactorEnabled: false - }); -}); |