forked from mirrors/misskey
Merge commit from fork
* fix(backend): Prevent the reuse of used TOTP tokens * fix * fix * tighten totp window
This commit is contained in:
parent
053e244582
commit
d323fe00d0
1 changed files with 34 additions and 6 deletions
|
|
@ -4,17 +4,18 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
import * as Redis from 'ioredis';
|
||||
import * as OTPAuth from 'otpauth';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserAuthService {
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
|
|
@ -30,16 +31,43 @@ export class UserAuthService {
|
|||
twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token),
|
||||
});
|
||||
} else {
|
||||
const delta = OTPAuth.TOTP.validate({
|
||||
// 1. 判定に用いるタイムスタンプを固定
|
||||
const now = Date.now();
|
||||
const normalizedToken = token.trim();
|
||||
const validationWindow = 1;
|
||||
const timeStep = 30; // TOTPの周期(秒)
|
||||
|
||||
// 2. TOTPインスタンスを生成(設定を一元管理するため)
|
||||
const totp = new OTPAuth.TOTP({
|
||||
secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!),
|
||||
digits: 6,
|
||||
token,
|
||||
window: 5,
|
||||
period: timeStep,
|
||||
});
|
||||
|
||||
// 3. 固定したタイムスタンプを使って検証
|
||||
const delta = totp.validate({
|
||||
token: normalizedToken,
|
||||
window: validationWindow,
|
||||
timestamp: now,
|
||||
});
|
||||
|
||||
if (delta === null) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
|
||||
// 4. totp.counter() を用い、同じタイムスタンプから基準ステップを取得
|
||||
const currentStep = totp.counter({ timestamp: now });
|
||||
const step = currentStep + delta;
|
||||
|
||||
const usedTokenRedisKey = `2fa:used:${profile.userId}:${step}`;
|
||||
|
||||
// 5. TTL(有効期限)の設定
|
||||
const ttl = timeStep * (validationWindow * 2 + 1);
|
||||
const setResult = await this.redisClient.set(usedTokenRedisKey, normalizedToken, 'EX', ttl, 'NX');
|
||||
|
||||
if (setResult === null) {
|
||||
throw new Error('authentication failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue