import { ILocalUser } from '@/models/entities/user.js'; import { Users, AccessTokens } from '@/models/index.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { userByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js'; import isNativeToken from './common/is-native-token.js'; export class AuthenticationError extends Error { constructor(message: string) { super(message); this.name = 'AuthenticationError'; } } export async function authenticate(authorization: string | null | undefined, bodyToken: string | null | undefined): Promise<[ILocalUser | null | undefined, AccessToken | null | undefined]> { let maybeToken: string | null = null; // check if there is an authorization header set if (authorization != null) { if (bodyToken != null) { throw new AuthenticationError('using multiple authorization schemes'); } // check if OAuth 2.0 Bearer tokens are being used // Authorization schemes are case insensitive if (authorization.substring(0, 7).toLowerCase() === 'bearer ') { maybeToken = authorization.substring(7); } else { throw new AuthenticationError('unsupported authentication scheme'); } } else if (bodyToken != null) { maybeToken = bodyToken; } else { return [null, null]; } const token: string = maybeToken; if (isNativeToken(token)) { const user = await localUserByNativeTokenCache.fetch(token); if (user == null) { throw new AuthenticationError('unknown token'); } return [user, null]; } else { const accessToken = await AccessTokens.findOne({ where: [{ hash: token.toLowerCase(), // app }, { token, // miauth }], }); if (accessToken == null) { throw new AuthenticationError('unknown token'); } AccessTokens.update(accessToken.id, { lastUsedAt: new Date(), }); const user = await userByIdCache.fetch(accessToken.userId); // can't authorize remote users if (!Users.isLocalUser(user)) return [null, null]; return [user, accessToken]; } }