92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
|
|
export interface IAuthRequest {
|
|
identity?: interfaces.data.IIdentity;
|
|
apiToken?: string;
|
|
}
|
|
|
|
export interface IAuthRequirement {
|
|
scope?: interfaces.data.TApiTokenScope;
|
|
requireAdminIdentity?: boolean;
|
|
requireAdminToken?: boolean;
|
|
}
|
|
|
|
export interface IAuthContext {
|
|
type: 'identity' | 'apiToken';
|
|
userId: string;
|
|
role?: string;
|
|
isAdmin: boolean;
|
|
scopes: interfaces.data.TApiTokenScope[];
|
|
identity?: interfaces.data.IIdentity;
|
|
token?: interfaces.data.IStoredApiToken;
|
|
}
|
|
|
|
const typedAuthError = (messageArg: string) => {
|
|
return new plugins.typedrequest.TypedResponseError(messageArg);
|
|
};
|
|
|
|
export async function requireOpsAuth(
|
|
opsServerRefArg: OpsServer,
|
|
requestArg: IAuthRequest,
|
|
requirementArg: IAuthRequirement = {},
|
|
): Promise<IAuthContext> {
|
|
let identityNeedsAdmin = false;
|
|
let tokenNeedsAdmin = false;
|
|
let tokenNeedsScope = false;
|
|
|
|
if (requestArg.identity?.jwt) {
|
|
const identity = await opsServerRefArg.adminHandler.validateIdentity(requestArg.identity);
|
|
if (identity) {
|
|
const isAdmin = identity.role === 'admin';
|
|
if (!requirementArg.requireAdminIdentity || isAdmin) {
|
|
return {
|
|
type: 'identity',
|
|
userId: identity.userId,
|
|
role: identity.role,
|
|
isAdmin,
|
|
scopes: [],
|
|
identity,
|
|
};
|
|
}
|
|
identityNeedsAdmin = true;
|
|
}
|
|
}
|
|
|
|
if (requestArg.apiToken) {
|
|
const tokenManager = opsServerRefArg.dcRouterRef.apiTokenManager;
|
|
const token = tokenManager ? await tokenManager.validateToken(requestArg.apiToken) : null;
|
|
if (token) {
|
|
if (requirementArg.requireAdminToken && token.policy?.role !== 'admin') {
|
|
tokenNeedsAdmin = true;
|
|
} else if (requirementArg.scope && !tokenManager!.hasScope(token, requirementArg.scope)) {
|
|
tokenNeedsScope = true;
|
|
} else {
|
|
const scopes = token.policy?.role === 'admin'
|
|
? ['*' as interfaces.data.TApiTokenScope]
|
|
: Array.from(new Set([...(token.scopes || []), ...(token.policy?.scopes || [])]));
|
|
return {
|
|
type: 'apiToken',
|
|
userId: token.createdBy,
|
|
role: token.policy?.role || 'operator',
|
|
isAdmin: token.policy?.role === 'admin',
|
|
scopes,
|
|
token,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tokenNeedsScope) {
|
|
throw typedAuthError('insufficient scope');
|
|
}
|
|
if (tokenNeedsAdmin) {
|
|
throw typedAuthError('admin API token required');
|
|
}
|
|
if (identityNeedsAdmin) {
|
|
throw typedAuthError('admin identity required');
|
|
}
|
|
throw typedAuthError('unauthorized');
|
|
}
|