Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 18 additions & 24 deletions src/main/ac-auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ClientError } from './exception.js';
import { AccessDeniedError, AuthenticationRequiredError } from '@nodescript/errors';

import { AuthContext } from './services/auth-context.js';

export interface AcJwtContext {
organisation_id?: string;
Expand Down Expand Up @@ -45,31 +47,36 @@ export interface AcJobAccessToken {
clientName: string;
}

export class AcAuth {
export class AcAuth extends AuthContext {

actor: AcActor | null;

actor: AcActor;
constructor(readonly jwtContext: AcJwtContext | null) {
super();
this.actor = jwtContext ? this.parseActor(jwtContext) : null;
}

constructor(jwtContext: AcJwtContext) {
this.actor = this.parseActor(jwtContext);
isAuthenticated() {
return this.actor != null;
}

getOrganisationId(): string | null {
return this.actor.organisationId ?? null;
return this.actor?.organisationId ?? null;
}

requireOrganisationId(): string {
const organisationId = this.getOrganisationId();
if (!organisationId) {
throw new AccessForbidden('organisationId is required');
throw new AccessDeniedError('organisationId is required');
}
return organisationId;
}

getClientId(): string | null {
if (this.actor.type === 'Client') {
if (this.actor?.type === 'Client') {
return this.actor.id;
}
if (this.actor.type === 'ServiceAccount' && this.actor.clientId) {
if (this.actor?.type === 'ServiceAccount' && this.actor?.clientId) {
return this.actor.clientId;
}
return null;
Expand All @@ -78,7 +85,7 @@ export class AcAuth {
requireClientId(): string {
const clientId = this.getClientId();
if (!clientId) {
throw new AccessForbidden('clientId is required');
throw new AccessDeniedError('clientId is required');
}
return clientId;
}
Expand All @@ -92,8 +99,7 @@ export class AcAuth {
this.parseClient(jwtContext) ??
this.parseUser(jwtContext);
if (actor == null) {
// TODO find what AcAuthProvider throws when isAuthenticated check is not met
throw new InvalidJwtTokenError('Could not parse actor from JWT payload');
throw new AuthenticationRequiredError('Could not parse actor from JWT payload');
}
return actor;
}
Expand Down Expand Up @@ -157,15 +163,3 @@ export class AcAuth {
}

}

export class AccessForbidden extends ClientError {

override status = 403;

}

export class InvalidJwtTokenError extends ClientError {

override status = 401;

}
36 changes: 0 additions & 36 deletions src/main/exception.ts

This file was deleted.

7 changes: 3 additions & 4 deletions src/main/http.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import cors from '@koa/cors';
import { ClientError } from '@nodescript/errors';
import { LogData, Logger, LogLevel } from '@nodescript/logger';
import http from 'http';
import https from 'https';
Expand All @@ -12,10 +13,9 @@ import { dep, Mesh } from 'mesh-ioc';
import stoppable, { StoppableServer } from 'stoppable';
import { constants } from 'zlib';

import { ClientError } from './exception.js';
import { standardMiddleware } from './middleware.js';
import { Router } from './router.js';
import { AuthContext, AuthProvider } from './services/index.js';
import { AuthProvider } from './services/index.js';
import { findMeshInstances } from './util.js';

interface MiddlewareSpec {
Expand Down Expand Up @@ -185,8 +185,7 @@ export class HttpServer extends Koa {
return async (ctx: Koa.Context, next: Koa.Next) => {
const mesh: Mesh = ctx.mesh;
const provider = mesh.resolve(AuthProvider);
const authContext = await provider.provide(ctx.headers);
mesh.constant(AuthContext, authContext);
await provider.provide(ctx, mesh);
return next();
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import * as util from './util.js';
export * from './ac-auth.js';
export * from './application.js';
export * from './doc.js';
export * from './exception.js';
export * from './http.js';
export * from './logger.js';
export * from './metrics/index.js';
Expand All @@ -13,6 +12,7 @@ export * from './schema.js';
export * from './services/index.js';
export * from './trial.js';
export * from './util.js';
export * from '@nodescript/errors';
export * from '@nodescript/logger';
export * from 'mesh-config';

Expand Down
20 changes: 11 additions & 9 deletions src/main/jwks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseError } from '@nodescript/errors';
import { Request } from '@ubio/request';
import Ajv from 'ajv';

import { ClientError, Exception } from './exception.js';
import { ajvErrorToMessage } from './util.js';

const ajv = new Ajv.default({
Expand Down Expand Up @@ -40,7 +40,7 @@ const jwksSchema = {
additionalProperties: true
};

const validateFunction = ajv.compile(jwksSchema);
const validateFunction = ajv.compile<SigningKeySets>(jwksSchema);

export class JwksClient {

Expand Down Expand Up @@ -95,9 +95,9 @@ export class JwksClient {
this._cache = null;
}

protected validateResponse(res: Record<string, any>): SigningKeySets {
protected validateResponse(res: any): SigningKeySets {
if (validateFunction(res) === true) {
return res as SigningKeySets;
return res;
}
const errors = validateFunction.errors || [];
const messages = errors.map(e => ajvErrorToMessage(e));
Expand Down Expand Up @@ -127,20 +127,22 @@ export interface SigningKey {
k: string;
}

export class SigningKeyNotFoundError extends Exception {
export class SigningKeyNotFoundError extends BaseError {

override message = 'Expected signing key not found in JWKS response';

}

export class JwksValidationError extends ClientError {
export class JwksValidationError extends BaseError {

override message = 'JWKS validation failed';

details: Record<string, any>;

constructor(messages: string[]) {
super();
this.details = {
messages
};
this.message = `JWKS validation failed: ${messages.join(', ')}`;
this.details = { messages };
}

}
3 changes: 1 addition & 2 deletions src/main/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ServerError } from '@nodescript/errors';
import { StructuredLogHttpRequest } from '@nodescript/logger';
import { Context } from 'koa';
import { v4 as uuid } from 'uuid';

import { ServerError } from './exception.js';

export async function standardMiddleware(ctx: Context, next: () => Promise<any>) {
let error: any = undefined;
const startedAt = Date.now();
Expand Down
10 changes: 7 additions & 3 deletions src/main/router.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ClientError, InitializationError } from '@nodescript/errors';
import { Logger } from '@nodescript/logger';
import { matchTokens, parsePath, PathToken } from '@nodescript/pathmatcher';
import Ajv, { ValidateFunction as AjvValidateFunction } from 'ajv';
Expand All @@ -6,7 +7,6 @@ import * as koa from 'koa';
import { config } from 'mesh-config';
import { dep } from 'mesh-ioc';

import { ClientError, Exception } from './exception.js';
import { GlobalMetrics } from './metrics/global.js';
import { ajvErrorToMessage, AnyConstructor, Constructor, deepClone } from './util.js';

Expand Down Expand Up @@ -77,7 +77,7 @@ function routeDecorator(method: string, spec: RouteSpec, role = RouteRole.ENDPOI
const bodyParams = params.filter(_ => _.source === 'body');
if (spec.requestBodySchema) {
if (bodyParams.length > 0) {
throw new Exception(
throw new InitializationError(
`${method} ${path}: BodyParams are only supported if requestBodySchema is not specified`);
}
}
Expand Down Expand Up @@ -274,7 +274,7 @@ function validateRouteDefinition(ep: RouteDefinition) {
const paramNamesSet = new Set<string>();
for (const param of ep.params) {
if (paramNamesSet.has(param.name)) {
throw new Exception(
throw new InitializationError(
`${ep.method} ${ep.path}: Parameter ${param.name} is declared more than once`
);
}
Expand Down Expand Up @@ -420,6 +420,8 @@ export class RequestParametersValidationError extends ClientError {

override status = 400;

details: Record<string, any>;

constructor(messages: string[]) {
super(`Invalid request parameters:\n${messages.map(_ => ` - ${_}`).join('\n')}`);
this.details = { messages };
Expand All @@ -431,6 +433,8 @@ export class ResponseValidationError extends ClientError {

override status = 500;

details: Record<string, any>;

constructor(messages: string[]) {
super(`Response body is not valid:\n${messages.map(_ => ` - ${_}`).join('\n')}`);
this.details = { messages };
Expand Down
5 changes: 4 additions & 1 deletion src/main/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ClientError } from '@nodescript/errors';
import Ajv, { ErrorObject, Options, ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';

import { ClientError } from './exception.js';
import { JsonSchema } from './schema-types.js';
import { ajvErrorToMessage } from './util.js';

Expand Down Expand Up @@ -98,6 +98,9 @@ export class Schema<T> {
export class ValidationError extends ClientError {

override status = 400;

details: Record<string, any>;

constructor(messages: string[]) {
super(`Validation failed:\n${messages.map(_ => ` - ${_}`).join('\n')}`);
this.details = {
Expand Down
Loading