Files
directus/api/src/services/tfa.ts
Aiden Foxx 084c6117b7 Modular authentication (#6942)
* Moved refactoring from LDAP branch

* Moved Auth into packages

* Updated frontend to support custom auth providers and make implementation more flexible

* Fixed exception handling and numerous bugs. Also added provider support to graphql

* Updated frontend to be able to set provider and identifier

* Fixed issue with setting the auth provider in app

* Updated package-lock.json

* Updated package-lock.json

* Cleanup, adding type handling and disabled changing provider

* Added title formatting to SSO links

* Fixed incorrect type export

* Fixed incorrect rc

* Update api/src/services/authentication.ts

* Updated sub-dependencies to rc87

* Fixed linting errors

* Prefer sending provider name as config var

* Pass clone of user info to auth provider instead of reference

* Moved auth from packages into core

* Removed generic login handler

* Fixed graphql complaint

* Moved exception back to api and cleaned up URLs

* Minor tweak

* Pulled across improvements from openid branch

* Fixed fix that wasn't a fix

* Update auth.ts

* Update auth.ts

* Update authentication.ts

* Update login-form.vue

* Regression fixes and cleanup

* Minor flow improvements

* Flipped if and fixed linting warning

* Un-expanded object that didn't need to be expanded!

* Trimmed auth interface for consistency when verifying passwords

* Removed auth-manager, changed login endpoint, broke out SSO links, removed username support, disabled updating external_identifier, generate provider options as part of field generation

* Cleaned up some code comments

* Use named exports in local driver

* Use async defaults for auth abstract class

* Use JSON for auth_data field

* Move session data blob to directus_sessions

* Remove unused export, rename auth->authDriver

* Opinionated changes

* Move login route registration to driver file

* Revert app changes in favor of PR #8277

* Send session token to auth provider and opinionated changes

* Added missing translation

* Fixed empty elements for users without email

* Update api/src/auth/drivers/local.ts

* Move pw verify to local driver, remove CRUD

* Opinions > logical reasoning

* Use session data, cleanup login method on auth serv

* Remove useless null

* Fixed breaking changes from refactor, and fixed build

* Fixed lint warning

* Ignore typescript nonsense

* Update api/src/services/authentication.ts

* Fix provider name passthrough

Co-authored-by: Aiden Foxx <aiden.foxx@sbab.se>
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
2021-09-27 17:18:20 -04:00

69 lines
2.2 KiB
TypeScript

import { Knex } from 'knex';
import { authenticator } from 'otplib';
import getDatabase from '../database';
import { InvalidPayloadException } from '../exceptions';
import { ItemsService } from './items';
import { AbstractServiceOptions, PrimaryKey } from '../types';
export class TFAService {
knex: Knex;
itemsService: ItemsService;
constructor(options: AbstractServiceOptions) {
this.knex = options.knex || getDatabase();
this.itemsService = new ItemsService('directus_users', options);
}
async verifyOTP(key: PrimaryKey, otp: string, secret?: string): Promise<boolean> {
if (secret) {
return authenticator.check(otp, secret);
}
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
if (!user?.tfa_secret) {
throw new InvalidPayloadException(`User "${key}" doesn't have TFA enabled.`);
}
return authenticator.check(otp, user.tfa_secret);
}
async generateTFA(key: PrimaryKey): Promise<Record<string, string>> {
const user = await this.knex.select('email', 'tfa_secret').from('directus_users').where({ id: key }).first();
if (user?.tfa_secret !== null) {
throw new InvalidPayloadException('TFA Secret is already set for this user');
}
if (!user?.email) {
throw new InvalidPayloadException('User must have a valid email to enable TFA');
}
const secret = authenticator.generateSecret();
const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
return {
secret,
url: authenticator.keyuri(user.email, project?.project_name || 'Directus', secret),
};
}
async enableTFA(key: PrimaryKey, otp: string, secret: string): Promise<void> {
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
if (user?.tfa_secret !== null) {
throw new InvalidPayloadException('TFA Secret is already set for this user');
}
if (!authenticator.check(otp, secret)) {
throw new InvalidPayloadException(`"otp" is invalid`);
}
await this.itemsService.updateOne(key, { tfa_secret: secret });
}
async disableTFA(key: PrimaryKey): Promise<void> {
await this.itemsService.updateOne(key, { tfa_secret: null });
}
}