diff --git a/generated/nwd-api/package.json b/generated/nwd-api/package.json index 2012955d..c837fa0d 100644 --- a/generated/nwd-api/package.json +++ b/generated/nwd-api/package.json @@ -42,9 +42,9 @@ "author": "", "license": "ISC", "dependencies": { - "@types/node": "^20.11.6", + "@types/node": "^20.11.7", "goodrouter": "^2.1.2", - "oa42-lib": "^0.6.4" + "oa42-lib": "^0.7.0" }, "devDependencies": { "typescript": "^5.3.3", diff --git a/generated/nwd-api/src/browser.ts b/generated/nwd-api/src/browser.ts index 5de4c6e0..dba50183 100644 --- a/generated/nwd-api/src/browser.ts +++ b/generated/nwd-api/src/browser.ts @@ -6,7 +6,7 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org export * from "./types.js"; export * from "./validators.js"; export * from "./parsers.js"; diff --git a/generated/nwd-api/src/client-server.test.ts b/generated/nwd-api/src/client-server.test.ts index c5eaff64..3cf1314d 100644 --- a/generated/nwd-api/src/client-server.test.ts +++ b/generated/nwd-api/src/client-server.test.ts @@ -6,13 +6,16 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org import assert from "assert/strict"; import test from "node:test"; import * as main from "./main.js"; import * as http from "http"; +type ServerAuthentication = { +apiToken: boolean, +}; test("get-main-categories 200 application/json", async () => { -const server = new main.Server({ +const server = new main.Server({ validateIncomingParameters: false, validateIncomingEntity: false, validateOutgoingParameters: false, @@ -27,6 +30,7 @@ contentType: "application/json", entity: () => main.mockMainCategory200GetSchema(), } }); +server.registerApiTokenAuthentication((credential) => credential === "super-secret-api-key") let lastError: unknown; const httpServer = http.createServer(); httpServer.addListener( @@ -46,7 +50,9 @@ const operationResult = await main.getMainCategories( contentType: null, parameters: {}, }, -{}, +{ +apiToken: "super-secret-api-key", +}, { baseUrl, validateIncomingParameters: false, @@ -71,7 +77,7 @@ httpServer.close((error) => (error == null ? resolve() : reject(error))), } }); test("create-main-category application/json 201 application/json", async () => { -const server = new main.Server({ +const server = new main.Server({ validateIncomingParameters: false, validateIncomingEntity: false, validateOutgoingParameters: false, @@ -92,6 +98,7 @@ contentType: "application/json", entity: () => main.mockMainCategoryPost201Schema(), } }); +server.registerApiTokenAuthentication((credential) => credential === "super-secret-api-key") let lastError: unknown; const httpServer = http.createServer(); httpServer.addListener( @@ -112,7 +119,9 @@ contentType: "application/json", parameters: {}, entity: () => main.mockMainCategoryPostRequestBodySchema(), }, -{}, +{ +apiToken: "super-secret-api-key", +}, { baseUrl, validateIncomingParameters: false, @@ -137,7 +146,7 @@ httpServer.close((error) => (error == null ? resolve() : reject(error))), } }); test("get-sub-categories 200 application/json", async () => { -const server = new main.Server({ +const server = new main.Server({ validateIncomingParameters: false, validateIncomingEntity: false, validateOutgoingParameters: false, @@ -157,6 +166,7 @@ contentType: "application/json", entity: () => main.mockSubCategoryMainCategoryId200GetSchema(), } }); +server.registerApiTokenAuthentication((credential) => credential === "super-secret-api-key") let lastError: unknown; const httpServer = http.createServer(); httpServer.addListener( @@ -178,7 +188,9 @@ parameters: { mainCategoryId: main.mockParametersSchema(), }, }, -{}, +{ +apiToken: "super-secret-api-key", +}, { baseUrl, validateIncomingParameters: false, @@ -203,7 +215,7 @@ httpServer.close((error) => (error == null ? resolve() : reject(error))), } }); test("create-sub-category application/json 201 application/json", async () => { -const server = new main.Server({ +const server = new main.Server({ validateIncomingParameters: false, validateIncomingEntity: false, validateOutgoingParameters: false, @@ -229,6 +241,7 @@ contentType: "application/json", entity: () => main.mockSubCategoryMainCategoryIdPost201Schema(), } }); +server.registerApiTokenAuthentication((credential) => credential === "super-secret-api-key") let lastError: unknown; const httpServer = http.createServer(); httpServer.addListener( @@ -251,7 +264,9 @@ mainCategoryId: main.mockParametersSchema(), }, entity: () => main.mockSubCategoryMainCategoryIdPostRequestBodySchema(), }, -{}, +{ +apiToken: "super-secret-api-key", +}, { baseUrl, validateIncomingParameters: false, diff --git a/generated/nwd-api/src/client.ts b/generated/nwd-api/src/client.ts index 3ef6a828..acf3b958 100644 --- a/generated/nwd-api/src/client.ts +++ b/generated/nwd-api/src/client.ts @@ -6,7 +6,7 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org import { Router } from "goodrouter"; import * as parameters from "./parameters.js"; import * as types from "./types.js"; @@ -35,7 +35,7 @@ Get main get-main-categories */ export async function getMainCategories( outgoingRequest: GetMainCategoriesOutgoingRequest, -credentials: unknown, +credentials: GetMainCategoriesCredentials, options: ClientOptions = defaultClientOptions, ): Promise { const { @@ -62,6 +62,7 @@ lastError.rule, ); } } +requestHeaders.append("api-token", credentials.apiToken); const path = router.stringifyRoute( 1, @@ -107,7 +108,7 @@ lastError.rule, } } if (responseContentType == null) { -throw new lib.MissingClientResponseContentType(); +throw new lib.ClientResponseMissingContentType(); } switch(responseContentType) { case "application/json": @@ -160,15 +161,18 @@ return entity; break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedContentType(); } break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedStatusCode(); } return incomingResponse; } +export type GetMainCategoriesCredentials = { +apiToken: string, +}; export type GetMainCategoriesOutgoingRequest = lib.OutgoingEmptyRequest ; @@ -185,7 +189,7 @@ Create an new main category */ export async function createMainCategory( outgoingRequest: CreateMainCategoryOutgoingRequest, -credentials: unknown, +credentials: CreateMainCategoryCredentials, options: ClientOptions = defaultClientOptions, ): Promise { const { @@ -212,6 +216,7 @@ lastError.rule, ); } } +requestHeaders.append("api-token", credentials.apiToken); const path = router.stringifyRoute( 1, @@ -296,7 +301,7 @@ lastError.rule, } } if (responseContentType == null) { -throw new lib.MissingClientResponseContentType(); +throw new lib.ClientResponseMissingContentType(); } switch(responseContentType) { case "application/json": @@ -349,15 +354,39 @@ return entity; break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedContentType(); +} +break; +} +case 403: +{ +const responseParameters = { +} as parameters.CreateMainCategory403ResponseParameters; +if(validateIncomingParameters) { +if(!parameters.isCreateMainCategory403ResponseParameters(responseParameters)) { +const lastError = parameters.getLastParameterValidationError(); +throw new lib.ClientResponseParameterValidationFailed( +lastError.parameterName, +lastError.path, +lastError.rule, +); +} +} +incomingResponse = { +status: fetchResponse.status, +contentType: null, +parameters: responseParameters, } break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedStatusCode(); } return incomingResponse; } +export type CreateMainCategoryCredentials = { +apiToken: string, +}; export type CreateMainCategoryOutgoingRequest = lib.OutgoingJsonRequest< parameters.CreateMainCategoryRequestParameters, @@ -372,13 +401,18 @@ parameters.CreateMainCategory201ResponseParameters, "application/json", types.MainCategoryPost201Schema > +| +lib.IncomingEmptyResponse< +403, +parameters.CreateMainCategory403ResponseParameters +> ; /** Get sub categories in a main category */ export async function getSubCategories( outgoingRequest: GetSubCategoriesOutgoingRequest, -credentials: unknown, +credentials: GetSubCategoriesCredentials, options: ClientOptions = defaultClientOptions, ): Promise { const { @@ -408,8 +442,9 @@ lastError.rule, lib.addParameter( pathParameters, "main-category-id", -outgoingRequest.parameters.mainCategoryId as unknown as string, +outgoingRequest.parameters.mainCategoryId == null ? "" : String(outgoingRequest.parameters.mainCategoryId), ); +requestHeaders.append("api-token", credentials.apiToken); const path = router.stringifyRoute( 2, @@ -455,7 +490,7 @@ lastError.rule, } } if (responseContentType == null) { -throw new lib.MissingClientResponseContentType(); +throw new lib.ClientResponseMissingContentType(); } switch(responseContentType) { case "application/json": @@ -508,15 +543,18 @@ return entity; break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedContentType(); } break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedStatusCode(); } return incomingResponse; } +export type GetSubCategoriesCredentials = { +apiToken: string, +}; export type GetSubCategoriesOutgoingRequest = lib.OutgoingEmptyRequest ; @@ -533,7 +571,7 @@ Create an new sub category in a main category */ export async function createSubCategory( outgoingRequest: CreateSubCategoryOutgoingRequest, -credentials: unknown, +credentials: CreateSubCategoryCredentials, options: ClientOptions = defaultClientOptions, ): Promise { const { @@ -563,8 +601,9 @@ lastError.rule, lib.addParameter( pathParameters, "main-category-id", -outgoingRequest.parameters.mainCategoryId as unknown as string, +outgoingRequest.parameters.mainCategoryId == null ? "" : String(outgoingRequest.parameters.mainCategoryId), ); +requestHeaders.append("api-token", credentials.apiToken); const path = router.stringifyRoute( 2, @@ -649,7 +688,7 @@ lastError.rule, } } if (responseContentType == null) { -throw new lib.MissingClientResponseContentType(); +throw new lib.ClientResponseMissingContentType(); } switch(responseContentType) { case "application/json": @@ -702,15 +741,39 @@ return entity; break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedContentType(); +} +break; +} +case 403: +{ +const responseParameters = { +} as parameters.CreateSubCategory403ResponseParameters; +if(validateIncomingParameters) { +if(!parameters.isCreateSubCategory403ResponseParameters(responseParameters)) { +const lastError = parameters.getLastParameterValidationError(); +throw new lib.ClientResponseParameterValidationFailed( +lastError.parameterName, +lastError.path, +lastError.rule, +); +} +} +incomingResponse = { +status: fetchResponse.status, +contentType: null, +parameters: responseParameters, } break; } default: -throw new lib.Unreachable(); +throw new lib.ClientResponseUnexpectedStatusCode(); } return incomingResponse; } +export type CreateSubCategoryCredentials = { +apiToken: string, +}; export type CreateSubCategoryOutgoingRequest = lib.OutgoingJsonRequest< parameters.CreateSubCategoryRequestParameters, @@ -725,4 +788,9 @@ parameters.CreateSubCategory201ResponseParameters, "application/json", types.SubCategoryMainCategoryIdPost201Schema > +| +lib.IncomingEmptyResponse< +403, +parameters.CreateSubCategory403ResponseParameters +> ; diff --git a/generated/nwd-api/src/main.ts b/generated/nwd-api/src/main.ts index c75371cf..979d3fc6 100644 --- a/generated/nwd-api/src/main.ts +++ b/generated/nwd-api/src/main.ts @@ -6,7 +6,7 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org export * from "oa42-lib"; export * from "./types.js"; export * from "./validators.js"; diff --git a/generated/nwd-api/src/parameters.ts b/generated/nwd-api/src/parameters.ts index c103cfe1..e0cd0f23 100644 --- a/generated/nwd-api/src/parameters.ts +++ b/generated/nwd-api/src/parameters.ts @@ -6,7 +6,7 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org import * as types from "./types.js"; import * as validators from "./validators.js"; import * as parsers from "./parsers.js"; @@ -64,6 +64,13 @@ return true; } export type CreateMainCategory201ResponseParameters = { }; +export function isCreateMainCategory403ResponseParameters( +parameters: Partial>, +): parameters is CreateMainCategory403ResponseParameters { +return true; +} +export type CreateMainCategory403ResponseParameters = { +}; export function isGetSubCategoriesRequestParameters( parameters: Partial>, ): parameters is GetSubCategoriesRequestParameters { @@ -136,3 +143,10 @@ return true; } export type CreateSubCategory201ResponseParameters = { }; +export function isCreateSubCategory403ResponseParameters( +parameters: Partial>, +): parameters is CreateSubCategory403ResponseParameters { +return true; +} +export type CreateSubCategory403ResponseParameters = { +}; diff --git a/generated/nwd-api/src/server.ts b/generated/nwd-api/src/server.ts index 40da399b..8cc6981f 100644 --- a/generated/nwd-api/src/server.ts +++ b/generated/nwd-api/src/server.ts @@ -6,7 +6,7 @@ // ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔═══╝ ██║╚════██║██╔═══╝ // ╚██████╔╝██║ ███████╗██║ ╚████║██║ ██║██║ ██║ ██║███████╗ // ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ -// v0.1.6 -- www.OpenApi42.org +// v0.2.1 -- www.OpenApi42.org import { Router } from "goodrouter"; import * as parameters from "./parameters.js"; import * as types from "./types.js"; @@ -29,7 +29,7 @@ const router = new Router({ parameterValueDecoder: value => value, parameterValueEncoder: value => value, }).loadFromJson({"rootNode":{"anchor":"","hasParameter":false,"routeKey":null,"children":[{"anchor":"/","hasParameter":false,"routeKey":null,"children":[{"anchor":"main-category","hasParameter":false,"routeKey":1,"children":[]},{"anchor":"sub-category/","hasParameter":false,"routeKey":null,"children":[{"anchor":"","hasParameter":true,"routeKey":2,"children":[]}]}]}]},"templatePairs":[[1,[["/main-category",null]]],[2,[["/sub-category/",null],["","main-category-id"]]]]}); -export type ServerAuthentication = Record; +export type ServerAuthentication = Record<"apiToken", unknown>; export class Server extends lib.ServerBase { @@ -81,6 +81,10 @@ default: throw new lib.NoRouteFound() } } +private apiTokenAuthenticationHandler?: ApiTokenAuthenticationHandler; +public registerApiTokenAuthentication(authenticationHandler: ApiTokenAuthenticationHandler) { +this.apiTokenAuthenticationHandler = authenticationHandler; +} private getMainCategoriesOperationHandler?: GetMainCategoriesOperationHandler; /** Get main get-main-categories @@ -108,8 +112,22 @@ const queryParameters = lib.parseParameters([serverIncomingRequest.query], "?", "&", "="); const cookieParameters = lib.parseParameters(cookie, "", "; ", "="); -const authentication = { +const credentials = { +apiToken: +lib.first(lib.getParameterValues(serverIncomingRequest.headers, "api-token")), } +const authentication: A = Object.fromEntries( +await Promise.all([ +( +async () => [ +"apiToken", +credentials.apiToken == null ? +undefined : +await this.apiTokenAuthenticationHandler?.(credentials.apiToken) +] +)(), +]), +); if(!isGetMainCategoriesAuthentication(authentication)) { throw new lib.AuthenticationFailed(); } @@ -231,8 +249,22 @@ const queryParameters = lib.parseParameters([serverIncomingRequest.query], "?", "&", "="); const cookieParameters = lib.parseParameters(cookie, "", "; ", "="); -const authentication = { +const credentials = { +apiToken: +lib.first(lib.getParameterValues(serverIncomingRequest.headers, "api-token")), } +const authentication: A = Object.fromEntries( +await Promise.all([ +( +async () => [ +"apiToken", +credentials.apiToken == null ? +undefined : +await this.apiTokenAuthenticationHandler?.(credentials.apiToken) +] +)(), +]), +); if(!isCreateMainCategoryAuthentication(authentication)) { throw new lib.AuthenticationFailed(); } @@ -250,7 +282,7 @@ lastError.rule } let incomingRequest: CreateMainCategoryIncomingRequest; if(requestContentType == null) { -throw new lib.MissingServerRequestContentType(); +throw new lib.ServerRequestMissingContentType(); } switch(requestContentType) { case "application/json": @@ -294,7 +326,7 @@ return entity; break; } default: -throw new lib.UnexpectedServerRequestContentType(); +throw new lib.ServerRequestUnexpectedContentType(); ; } const outgoingResponse = await this.createMainCategoryOperationHandler?.( @@ -366,6 +398,25 @@ throw new lib.Unreachable(); } break; } +case 403: +{ +if(validateOutgoingParameters) { +if(!parameters.isCreateMainCategory403ResponseParameters(outgoingResponse.parameters)) { +const lastError = parameters.getLastParameterValidationError(); +throw new lib.ServerResponseParameterValidationFailed( +lastError.parameterName, +lastError.path, +lastError.rule, +); +} +} +const responseHeaders = {}; +serverOutgoingResponse = { +status: outgoingResponse.status, +headers: responseHeaders, +} +break; +} default: throw new lib.Unreachable(); } @@ -398,8 +449,22 @@ const queryParameters = lib.parseParameters([serverIncomingRequest.query], "?", "&", "="); const cookieParameters = lib.parseParameters(cookie, "", "; ", "="); -const authentication = { +const credentials = { +apiToken: +lib.first(lib.getParameterValues(serverIncomingRequest.headers, "api-token")), } +const authentication: A = Object.fromEntries( +await Promise.all([ +( +async () => [ +"apiToken", +credentials.apiToken == null ? +undefined : +await this.apiTokenAuthenticationHandler?.(credentials.apiToken) +] +)(), +]), +); if(!isGetSubCategoriesAuthentication(authentication)) { throw new lib.AuthenticationFailed(); } @@ -523,8 +588,22 @@ const queryParameters = lib.parseParameters([serverIncomingRequest.query], "?", "&", "="); const cookieParameters = lib.parseParameters(cookie, "", "; ", "="); -const authentication = { +const credentials = { +apiToken: +lib.first(lib.getParameterValues(serverIncomingRequest.headers, "api-token")), } +const authentication: A = Object.fromEntries( +await Promise.all([ +( +async () => [ +"apiToken", +credentials.apiToken == null ? +undefined : +await this.apiTokenAuthenticationHandler?.(credentials.apiToken) +] +)(), +]), +); if(!isCreateSubCategoryAuthentication(authentication)) { throw new lib.AuthenticationFailed(); } @@ -544,7 +623,7 @@ lastError.rule } let incomingRequest: CreateSubCategoryIncomingRequest; if(requestContentType == null) { -throw new lib.MissingServerRequestContentType(); +throw new lib.ServerRequestMissingContentType(); } switch(requestContentType) { case "application/json": @@ -588,7 +667,7 @@ return entity; break; } default: -throw new lib.UnexpectedServerRequestContentType(); +throw new lib.ServerRequestUnexpectedContentType(); ; } const outgoingResponse = await this.createSubCategoryOperationHandler?.( @@ -660,20 +739,42 @@ throw new lib.Unreachable(); } break; } +case 403: +{ +if(validateOutgoingParameters) { +if(!parameters.isCreateSubCategory403ResponseParameters(outgoingResponse.parameters)) { +const lastError = parameters.getLastParameterValidationError(); +throw new lib.ServerResponseParameterValidationFailed( +lastError.parameterName, +lastError.path, +lastError.rule, +); +} +} +const responseHeaders = {}; +serverOutgoingResponse = { +status: outgoingResponse.status, +headers: responseHeaders, +} +break; +} default: throw new lib.Unreachable(); } return serverOutgoingResponse } } +export type ApiTokenAuthenticationHandler = +(credential: string) => +A["apiToken"] | undefined | +Promise; export function isGetMainCategoriesAuthentication( authentication: Partial>, ): authentication is GetMainCategoriesAuthentication { -// TODO -return true; +return authentication.apiToken !== undefined } export type GetMainCategoriesAuthentication = -{} +Pick ; export type GetMainCategoriesOperationHandler = ( @@ -694,11 +795,10 @@ types.MainCategory200GetSchema export function isCreateMainCategoryAuthentication( authentication: Partial>, ): authentication is CreateMainCategoryAuthentication { -// TODO -return true; +return authentication.apiToken !== undefined } export type CreateMainCategoryAuthentication = -{} +Pick ; export type CreateMainCategoryOperationHandler = ( @@ -719,15 +819,19 @@ parameters.CreateMainCategory201ResponseParameters, "application/json", types.MainCategoryPost201Schema > +| +lib.OutgoingEmptyResponse< +403, +parameters.CreateMainCategory403ResponseParameters +> ; export function isGetSubCategoriesAuthentication( authentication: Partial>, ): authentication is GetSubCategoriesAuthentication { -// TODO -return true; +return authentication.apiToken !== undefined } export type GetSubCategoriesAuthentication = -{} +Pick ; export type GetSubCategoriesOperationHandler = ( @@ -748,11 +852,10 @@ types.SubCategoryMainCategoryId200GetSchema export function isCreateSubCategoryAuthentication( authentication: Partial>, ): authentication is CreateSubCategoryAuthentication { -// TODO -return true; +return authentication.apiToken !== undefined } export type CreateSubCategoryAuthentication = -{} +Pick ; export type CreateSubCategoryOperationHandler = ( @@ -773,4 +876,9 @@ parameters.CreateSubCategory201ResponseParameters, "application/json", types.SubCategoryMainCategoryIdPost201Schema > +| +lib.OutgoingEmptyResponse< +403, +parameters.CreateSubCategory403ResponseParameters +> ; diff --git a/package-lock.json b/package-lock.json index 384a8937..55faf91a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ ], "devDependencies": { "cspell": "^8.3.2", - "oa42-generator": "^0.1.6", + "oa42-generator": "^0.2.1", "prettier": "^3.2.4" } }, @@ -18,9 +18,9 @@ "version": "0.0.0", "license": "ISC", "dependencies": { - "@types/node": "^20.11.6", + "@types/node": "^20.11.7", "goodrouter": "^2.1.2", - "oa42-lib": "^0.6.4" + "oa42-lib": "^0.7.0" }, "devDependencies": { "@tsconfig/node20": "^20.1.2", @@ -554,16 +554,14 @@ }, "node_modules/@types/node": { "version": "20.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz", - "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/pg": { "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.0.tgz", - "integrity": "sha512-sDAlRiBNthGjNFfvt0k6mtotoVYVQ63pA8R4EMWka7crawSR60waVYR0HAgmPRs/e2YaeJTD/43OoZ3PFw80pw==", + "license": "MIT", "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -572,18 +570,16 @@ }, "node_modules/@types/yargs": { "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ansi-regex": { "version": "6.0.1", @@ -658,9 +654,8 @@ }, "node_modules/camelcase": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, @@ -1317,9 +1312,8 @@ }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -1693,9 +1687,8 @@ }, "node_modules/hash.js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -1866,8 +1859,6 @@ }, "node_modules/jns42-generator": { "version": "0.12.10", - "resolved": "https://registry.npmjs.org/jns42-generator/-/jns42-generator-0.12.10.tgz", - "integrity": "sha512-IvgITGTDB0ImQA0QUNn7as03e8qR7l8xcR/QUWdGaW7NjbgATDi8dYFixX8xaSDC9ntnQrBfZBIdY0AnqTw4CA==", "bundleDependencies": [ "jns42-optimizer", "schema-draft-04", @@ -1875,6 +1866,7 @@ "schema-intermediate" ], "dev": true, + "license": "ISC", "dependencies": { "@types/node": "^20.11.0", "@types/yargs": "^17.0.32", @@ -2022,9 +2014,8 @@ }, "node_modules/minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/minimatch": { "version": "9.0.3", @@ -2071,22 +2062,21 @@ "link": true }, "node_modules/oa42-generator": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/oa42-generator/-/oa42-generator-0.1.6.tgz", - "integrity": "sha512-SFKpnk93mJ9LzKlZQBgzxPyBwIq5ZsYO10mCSJgHALYNzuzHrQu6JHDnqfvxsVY/D67Lmh2c988+Zn9VVU4VFQ==", + "version": "0.2.1", "bundleDependencies": [ "schema-oas-v3-0", "schema-oas-v3-1", "schema-swagger-v2" ], "dev": true, + "license": "ISC", "dependencies": { - "@types/node": "^20.11.6", + "@types/node": "^20.11.7", "@types/yargs": "^17.0.32", "camelcase": "^8.0.0", "goodrouter": "^2.1.2", "jns42-generator": "^0.12.10", - "oa42-lib": "^0.6.4", + "oa42-lib": "^0.7.0", "schema-oas-v3-0": "^0.0.0", "schema-oas-v3-1": "^0.0.0", "schema-swagger-v2": "^0.0.0", @@ -2106,7 +2096,7 @@ "inBundle": true, "license": "ISC", "dependencies": { - "@types/node": "^20.11.0" + "@types/node": "^20.11.7" } }, "node_modules/oa42-generator/node_modules/schema-oas-v3-1": { @@ -2115,7 +2105,7 @@ "inBundle": true, "license": "ISC", "dependencies": { - "@types/node": "^20.11.0" + "@types/node": "^20.11.7" } }, "node_modules/oa42-generator/node_modules/schema-swagger-v2": { @@ -2124,16 +2114,16 @@ "inBundle": true, "license": "ISC", "dependencies": { - "@types/node": "^20.11.0" + "@types/node": "^20.11.7" } }, "node_modules/oa42-lib": { - "version": "0.6.4", + "version": "0.7.0", "license": "ISC", "dependencies": { - "@types/node": "^20.11.4", + "@types/node": "^20.11.6", "tslib": "^2.6.2", - "type-fest": "^4.9.0" + "type-fest": "^4.10.1" } }, "node_modules/obuf": { @@ -2687,8 +2677,7 @@ }, "node_modules/type-fest": { "version": "4.10.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", - "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index fd7e6a73..01584911 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "cspell": "^8.3.2", - "oa42-generator": "^0.1.6", + "oa42-generator": "^0.2.1", "prettier": "^3.2.4" } } diff --git a/packages/nwd-api-server/src/application/authentication.ts b/packages/nwd-api-server/src/application/authentication.ts index 15f9da5c..16c2eaae 100644 --- a/packages/nwd-api-server/src/application/authentication.ts +++ b/packages/nwd-api-server/src/application/authentication.ts @@ -1 +1,6 @@ -export type Authentication = {}; +export type Authentication = { + apiToken: { + userId: number; + super: boolean; + }; +}; diff --git a/packages/nwd-api-server/src/application/server.ts b/packages/nwd-api-server/src/application/server.ts index 7054c7f7..329bd1dd 100644 --- a/packages/nwd-api-server/src/application/server.ts +++ b/packages/nwd-api-server/src/application/server.ts @@ -1,4 +1,5 @@ import * as api from "nwd-api"; +import * as authenticationHandlers from "../authentication-handlers/index.js"; import * as operationHandlers from "../operation-handlers/index.js"; import { Authentication } from "./authentication.js"; import { Context } from "./context.js"; @@ -16,6 +17,10 @@ export function createApplicationServer(context: Context, onError?: (error: unkn server.registerGetSubCategoriesOperation(operationHandlers.getSubCategories(context)); server.registerCreateSubCategoryOperation(operationHandlers.createSubCategory(context)); + // authentication + + server.registerApiTokenAuthentication(authenticationHandlers.apiToken(context)); + // middleware! server.registerMiddleware(api.createErrorMiddleware(onError)); diff --git a/packages/nwd-api-server/src/authentication-handlers/api-token.ts b/packages/nwd-api-server/src/authentication-handlers/api-token.ts new file mode 100644 index 00000000..b938384d --- /dev/null +++ b/packages/nwd-api-server/src/authentication-handlers/api-token.ts @@ -0,0 +1,20 @@ +import { ApiTokenAuthenticationHandler } from "nwd-api"; +import * as application from "../application/index.js"; + +export function apiToken( + context: application.Context, +): ApiTokenAuthenticationHandler { + return (credential) => { + switch (credential) { + case "supersecret": + return { + userId: 1, + super: true, + }; + + default: + // TODO This should return nothing once the generator supports it + return undefined as any; + } + }; +} diff --git a/packages/nwd-api-server/src/authentication-handlers/index.ts b/packages/nwd-api-server/src/authentication-handlers/index.ts new file mode 100644 index 00000000..44615b28 --- /dev/null +++ b/packages/nwd-api-server/src/authentication-handlers/index.ts @@ -0,0 +1 @@ +export * from "./api-token.js"; diff --git a/packages/nwd-api-server/src/operation-handlers/categories/create-main-category.ts b/packages/nwd-api-server/src/operation-handlers/categories/create-main-category.ts index 2f9f90c2..f8ca1e65 100644 --- a/packages/nwd-api-server/src/operation-handlers/categories/create-main-category.ts +++ b/packages/nwd-api-server/src/operation-handlers/categories/create-main-category.ts @@ -7,6 +7,14 @@ export function createMainCategory( context: application.Context, ): api.CreateMainCategoryOperationHandler { return async (incomingRequest, authentication) => { + if (!authentication.apiToken.super) { + return { + status: 403, + parameters: {}, + contentType: null, + }; + } + const entity = await incomingRequest.entity(); const rows = await context.db diff --git a/packages/nwd-api-server/src/operation-handlers/categories/create-sub-category.ts b/packages/nwd-api-server/src/operation-handlers/categories/create-sub-category.ts index b3dd0b9b..4f2d629f 100644 --- a/packages/nwd-api-server/src/operation-handlers/categories/create-sub-category.ts +++ b/packages/nwd-api-server/src/operation-handlers/categories/create-sub-category.ts @@ -7,6 +7,14 @@ export function createSubCategory( context: application.Context, ): api.CreateSubCategoryOperationHandler { return async (incomingRequest, authentication) => { + if (!authentication.apiToken.super) { + return { + status: 403, + parameters: {}, + contentType: null, + }; + } + const { mainCategoryId } = incomingRequest.parameters; const entity = await incomingRequest.entity(); diff --git a/packages/nwd-api-server/src/operation-handlers/categories/crud.test.ts b/packages/nwd-api-server/src/operation-handlers/categories/crud.test.ts index f9d66e59..3030f350 100644 --- a/packages/nwd-api-server/src/operation-handlers/categories/crud.test.ts +++ b/packages/nwd-api-server/src/operation-handlers/categories/crud.test.ts @@ -7,6 +7,7 @@ import { withServer } from "../../testing/index.js"; test("categories crud", () => withDatabase(async ({ db }) => withServer({ db }, async ({ baseUrl, server }) => { + const clientCredentials = { apiToken: "supersecret" }; const clientConfiguration = { baseUrl }; let mainCategoryId: number; @@ -19,11 +20,11 @@ test("categories crud", () => name: "main-category", }), }, - {}, + clientCredentials, clientConfiguration, ); - assert.equal(operationResult.status, 201); + assert(operationResult.status === 201); const entity = await operationResult.entity(); mainCategoryId = entity.id; @@ -40,11 +41,11 @@ test("categories crud", () => parameters: {}, contentType: null, }, - {}, + clientCredentials, clientConfiguration, ); - assert.equal(operationResult.status, 200); + assert(operationResult.status === 200); const entity = await operationResult.entity(); @@ -67,11 +68,11 @@ test("categories crud", () => name: "sub-category", }), }, - {}, + clientCredentials, clientConfiguration, ); - assert.equal(operationResult.status, 201); + assert(operationResult.status === 201); const entity = await operationResult.entity(); assert.deepEqual(entity, { @@ -88,11 +89,11 @@ test("categories crud", () => }, contentType: null, }, - {}, + clientCredentials, clientConfiguration, ); - assert.equal(operationResult.status, 200); + assert(operationResult.status === 200); const entity = await operationResult.entity(); diff --git a/specifications/nwd-api.yaml b/specifications/nwd-api.yaml index ad44c40e..7de057f4 100644 --- a/specifications/nwd-api.yaml +++ b/specifications/nwd-api.yaml @@ -6,6 +6,9 @@ info: Nationaal Watersport Diploma API version: 0.1.0 +security: + - api-token: [] + paths: /main-category: get: @@ -35,6 +38,8 @@ paths: application/json: schema: $ref: "#/components/schemas/main-category" + "403": + $ref: "#/components/responses/forbidden" /sub-category/{main-category-id}: parameters: @@ -70,6 +75,8 @@ paths: application/json: schema: $ref: "#/components/schemas/sub-category" + "403": + $ref: "#/components/responses/forbidden" components: schemas: @@ -140,3 +147,13 @@ components: type: string minLength: 1 maxLength: 100 + + responses: + forbidden: + description: Forbidden + + securitySchemes: + api-token: + type: apiKey + name: api-token + in: header