From c2fd0c333e8f686822f5c253f72edd2ae7434947 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Thu, 26 Feb 2026 13:48:29 +0100 Subject: [PATCH 1/5] chore: dependency updates --- common/package.json | 4 +- common/schemas/boilerplate-api.json | 262 +++++++- common/schemas/entities.json | 16 +- common/src/boilerplate-api.ts | 8 + common/src/models/user.ts | 5 +- frontend/package.json | 17 +- frontend/src/components/github-logo/index.tsx | 9 +- frontend/src/components/header.tsx | 6 +- frontend/src/pages/hello-world.tsx | 41 +- .../src/services/boilerplate-api-client.ts | 26 +- frontend/src/services/session.ts | 16 +- package.json | 14 +- service/package.json | 17 +- service/src/authorization/authorized-only.ts | 21 + service/src/config.ts | 54 -- service/src/get-cors-options.ts | 10 + service/src/get-port.ts | 5 + service/src/root-injector.ts | 7 + service/src/seed.ts | 75 ++- service/src/service.ts | 75 +-- service/src/setup-rest-api.ts | 67 ++ service/src/setup-store.ts | 46 ++ yarn.lock | 633 +++++++++++------- 23 files changed, 934 insertions(+), 500 deletions(-) create mode 100644 service/src/authorization/authorized-only.ts delete mode 100644 service/src/config.ts create mode 100644 service/src/get-cors-options.ts create mode 100644 service/src/get-port.ts create mode 100644 service/src/root-injector.ts create mode 100644 service/src/setup-rest-api.ts create mode 100644 service/src/setup-store.ts diff --git a/common/package.json b/common/package.json index cf706089..06936408 100644 --- a/common/package.json +++ b/common/package.json @@ -25,11 +25,11 @@ "create-schemas": "node ./dist/bin/create-schemas.js" }, "devDependencies": { - "@types/node": "^25.2.2", + "@types/node": "^25.3.1", "ts-json-schema-generator": "^2.5.0", "vitest": "^4.0.18" }, "dependencies": { - "@furystack/rest": "^8.0.35" + "@furystack/rest": "^8.0.39" } } diff --git a/common/schemas/boilerplate-api.json b/common/schemas/boilerplate-api.json index fbee5b65..c437d34a 100644 --- a/common/schemas/boilerplate-api.json +++ b/common/schemas/boilerplate-api.json @@ -11,7 +11,9 @@ "type": "string" } }, - "required": ["param1"], + "required": [ + "param1" + ], "additionalProperties": false }, "result": { @@ -21,11 +23,16 @@ "type": "string" } }, - "required": ["param1Value"], + "required": [ + "param1Value" + ], "additionalProperties": false } }, - "required": ["query", "result"], + "required": [ + "query", + "result" + ], "additionalProperties": false }, "TestUrlParamsEndpoint": { @@ -38,7 +45,9 @@ "type": "string" } }, - "required": ["urlParam"], + "required": [ + "urlParam" + ], "additionalProperties": false }, "result": { @@ -48,11 +57,16 @@ "type": "string" } }, - "required": ["urlParamValue"], + "required": [ + "urlParamValue" + ], "additionalProperties": false } }, - "required": ["url", "result"], + "required": [ + "url", + "result" + ], "additionalProperties": false }, "TestPostBodyEndpoint": { @@ -65,7 +79,9 @@ "type": "string" } }, - "required": ["value"], + "required": [ + "value" + ], "additionalProperties": false }, "result": { @@ -75,11 +91,41 @@ "type": "string" } }, - "required": ["bodyValue"], + "required": [ + "bodyValue" + ], "additionalProperties": false } }, - "required": ["body", "result"], + "required": [ + "body", + "result" + ], + "additionalProperties": false + }, + "TestAuthorizedEndpoint": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "message", + "timestamp" + ], + "additionalProperties": false + } + }, + "required": [ + "result" + ], "additionalProperties": false }, "BoilerplateApi": { @@ -98,11 +144,15 @@ "type": "boolean" } }, - "required": ["isAuthenticated"], + "required": [ + "isAuthenticated" + ], "additionalProperties": false } }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false }, "/currentUser": { @@ -112,7 +162,9 @@ "$ref": "#/definitions/User" } }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false }, "/testQuery": { @@ -120,9 +172,18 @@ }, "/testUrlParams/:urlParam": { "$ref": "#/definitions/TestUrlParamsEndpoint" + }, + "/testAuthorized": { + "$ref": "#/definitions/TestAuthorizedEndpoint" } }, - "required": ["/isAuthenticated", "/currentUser", "/testQuery", "/testUrlParams/:urlParam"], + "required": [ + "/isAuthenticated", + "/currentUser", + "/testQuery", + "/testUrlParams/:urlParam", + "/testAuthorized" + ], "additionalProperties": false }, "POST": { @@ -144,11 +205,17 @@ "type": "string" } }, - "required": ["username", "password"], + "required": [ + "username", + "password" + ], "additionalProperties": false } }, - "required": ["result", "body"], + "required": [ + "result", + "body" + ], "additionalProperties": false }, "/logout": { @@ -156,14 +223,126 @@ "properties": { "result": {} }, - "required": ["result"], + "required": [ + "result" + ], + "additionalProperties": false + }, + "/jwt/login": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "refreshToken" + ], + "additionalProperties": false + } + }, + "required": [ + "body", + "result" + ], + "additionalProperties": false + }, + "/jwt/refresh": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + }, + "required": [ + "refreshToken" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "refreshToken" + ], + "additionalProperties": false + } + }, + "required": [ + "body", + "result" + ], + "additionalProperties": false + }, + "/jwt/logout": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + }, + "required": [ + "refreshToken" + ], + "additionalProperties": false + }, + "result": {} + }, + "required": [ + "body", + "result" + ], "additionalProperties": false }, "/testPostBody": { "$ref": "#/definitions/TestPostBodyEndpoint" } }, - "required": ["/login", "/logout", "/testPostBody"], + "required": [ + "/login", + "/logout", + "/jwt/login", + "/jwt/refresh", + "/jwt/logout", + "/testPostBody" + ], "additionalProperties": false }, "PATCH": { @@ -177,7 +356,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -192,7 +373,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -207,7 +390,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -222,7 +407,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -237,7 +424,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -252,7 +441,9 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } }, @@ -267,29 +458,40 @@ "body": {}, "headers": {} }, - "required": ["result"], + "required": [ + "result" + ], "additionalProperties": false } } }, - "required": ["GET", "POST"], + "required": [ + "GET", + "POST" + ], "additionalProperties": false }, "User": { "type": "object", "properties": { "username": { - "type": "string" + "type": "string", + "description": "Name of the user" }, "roles": { "type": "array", "items": { "type": "string" - } + }, + "description": "List of roles" } }, - "required": ["username", "roles"], - "additionalProperties": false + "required": [ + "username", + "roles" + ], + "additionalProperties": false, + "description": "Class model that represents an application user" } } -} +} \ No newline at end of file diff --git a/common/schemas/entities.json b/common/schemas/entities.json index ec293511..91fb5925 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -6,17 +6,23 @@ "type": "object", "properties": { "username": { - "type": "string" + "type": "string", + "description": "Name of the user" }, "roles": { "type": "array", "items": { "type": "string" - } + }, + "description": "List of roles" } }, - "required": ["username", "roles"], - "additionalProperties": false + "required": [ + "username", + "roles" + ], + "additionalProperties": false, + "description": "Class model that represents an application user" } } -} +} \ No newline at end of file diff --git a/common/src/boilerplate-api.ts b/common/src/boilerplate-api.ts index ff2299b3..1a09c245 100644 --- a/common/src/boilerplate-api.ts +++ b/common/src/boilerplate-api.ts @@ -4,6 +4,7 @@ import type { User } from './models/index.js' export type TestQueryEndpoint = { query: { param1: string }; result: { param1Value: string } } export type TestUrlParamsEndpoint = { url: { urlParam: string }; result: { urlParamValue: string } } export type TestPostBodyEndpoint = { body: { value: string }; result: { bodyValue: string } } +export type TestAuthorizedEndpoint = { result: { message: string; timestamp: string } } export interface BoilerplateApi extends RestApi { GET: { @@ -11,10 +12,17 @@ export interface BoilerplateApi extends RestApi { '/currentUser': { result: User } '/testQuery': TestQueryEndpoint '/testUrlParams/:urlParam': TestUrlParamsEndpoint + '/testAuthorized': TestAuthorizedEndpoint } POST: { '/login': { result: User; body: { username: string; password: string } } '/logout': { result: unknown } + '/jwt/login': { + body: { username: string; password: string } + result: { accessToken: string; refreshToken: string } + } + '/jwt/refresh': { body: { refreshToken: string }; result: { accessToken: string; refreshToken: string } } + '/jwt/logout': { body: { refreshToken: string }; result: unknown } '/testPostBody': TestPostBodyEndpoint } } diff --git a/common/src/models/user.ts b/common/src/models/user.ts index 18b7f241..96ee3349 100644 --- a/common/src/models/user.ts +++ b/common/src/models/user.ts @@ -1,4 +1 @@ -export class User { - public username!: string - roles: string[] = [] -} +export { User } from '@furystack/core' diff --git a/frontend/package.json b/frontend/package.json index d651f79b..5502ec29 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,14 +17,15 @@ "vitest": "^4.0.18" }, "dependencies": { - "@furystack/core": "^15.0.35", - "@furystack/inject": "^12.0.29", - "@furystack/logging": "^8.0.29", - "@furystack/rest-client-fetch": "^8.0.35", - "@furystack/shades": "^12.0.0", - "@furystack/shades-common-components": "^12.0.0", - "@furystack/utils": "^8.1.9", - "@types/node": "^25.2.2", + "@furystack/auth-jwt": "^1.0.0", + "@furystack/core": "^15.2.1", + "@furystack/inject": "^12.0.30", + "@furystack/logging": "^8.0.30", + "@furystack/rest-client-fetch": "^8.0.39", + "@furystack/shades": "^12.2.3", + "@furystack/shades-common-components": "^13.0.0", + "@furystack/utils": "^8.1.10", + "@types/node": "^25.3.1", "common": "workspace:^" } } diff --git a/frontend/src/components/github-logo/index.tsx b/frontend/src/components/github-logo/index.tsx index e99a6278..a17c86fc 100644 --- a/frontend/src/components/github-logo/index.tsx +++ b/frontend/src/components/github-logo/index.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { createComponent, Shade } from '@furystack/shades' -import { ThemeProviderService } from '@furystack/shades-common-components' +import { getTextColor, ThemeProviderService } from '@furystack/shades-common-components' // @ts-ignore import ghLight from './gh-light.png' // @ts-ignore @@ -15,13 +15,10 @@ export const GithubLogo = Shade({ render: ({ props, useDisposable, useState, injector }) => { const themeProvider = injector.getInstance(ThemeProviderService) - const [theme, setTheme] = useState( - 'themeName', - themeProvider.getTextColor(themeProvider.theme.background.paper, 'light', 'dark'), - ) + const [theme, setTheme] = useState('themeName', getTextColor(themeProvider.theme.background.paper, 'light', 'dark')) useDisposable('themeChange', () => themeProvider.subscribe('themeChanged', () => { - const value = themeProvider.getTextColor(themeProvider.theme.background.paper, 'light', 'dark') + const value = getTextColor(themeProvider.theme.background.paper, 'light', 'dark') setTheme(value) }), ) diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index 96f1db6e..c1802034 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -1,5 +1,5 @@ import { createComponent, NestedRouteLink, Shade } from '@furystack/shades' -import { AppBar, Button, DrawerToggleButton } from '@furystack/shades-common-components' +import { AppBar, Button, DrawerToggleButton, Typography } from '@furystack/shades-common-components' import { environmentOptions } from '../environment-options.js' import { SessionService } from '../services/session.js' import { GithubLogo } from './github-logo/index.js' @@ -11,11 +11,11 @@ export const Header = Shade({ return ( -

+ 🧩 FuryStack Boilerplate -

+
diff --git a/frontend/src/pages/hello-world.tsx b/frontend/src/pages/hello-world.tsx index 237fd28b..55b5f044 100644 --- a/frontend/src/pages/hello-world.tsx +++ b/frontend/src/pages/hello-world.tsx @@ -1,11 +1,27 @@ import { Shade, createComponent } from '@furystack/shades' -import { PageContainer, PageHeader, Typography } from '@furystack/shades-common-components' +import { Button, PageContainer, PageHeader, Typography } from '@furystack/shades-common-components' +import { BoilerplateApiClient } from '../services/boilerplate-api-client.js' import { SessionService } from '../services/session.js' export const HelloWorld = Shade({ shadowDomName: 'hello-world', - render: ({ useObservable, injector }) => { + render: ({ useObservable, useState, injector }) => { const [currentUser] = useObservable('userName', injector.getInstance(SessionService).currentUser) + const [authorizedResult, setAuthorizedResult] = useState('authorizedResult', '') + const [authorizedError, setAuthorizedError] = useState('authorizedError', '') + + const handleTestAuthorized = async () => { + setAuthorizedResult('') + setAuthorizedError('') + try { + const apiClient = injector.getInstance(BoilerplateApiClient) + const { result } = await apiClient.call({ method: 'GET', action: '/testAuthorized' }) + setAuthorizedResult(`${result.message} (${result.timestamp})`) + } catch (error) { + setAuthorizedError(error instanceof Error ? error.message : 'Request failed') + } + } + return ( +
+ + Authorized Endpoint Test + + + Call a protected endpoint that requires a valid access token. Useful for testing token refresh after + expiration. + +
+ +
+ {authorizedResult && ( + + {authorizedResult} + + )} + {authorizedError && {authorizedError}} +
+ Egyesült Államok diff --git a/frontend/src/services/boilerplate-api-client.ts b/frontend/src/services/boilerplate-api-client.ts index bfd002d4..899d7ac2 100644 --- a/frontend/src/services/boilerplate-api-client.ts +++ b/frontend/src/services/boilerplate-api-client.ts @@ -1,15 +1,27 @@ +import { createJwtClient } from '@furystack/auth-jwt/client' import { Injectable } from '@furystack/inject' import { createClient } from '@furystack/rest-client-fetch' import type { BoilerplateApi } from 'common' import { environmentOptions } from '../environment-options.js' +type JwtClient = ReturnType> +type ApiCall = ReturnType> + +const jwtClient: JwtClient = createJwtClient( + { endpointUrl: environmentOptions.serviceUrl, refreshThresholdSeconds: 10 }, + '/jwt/login', + '/jwt/refresh', + '/jwt/logout', +) + @Injectable({ lifetime: 'singleton' }) export class BoilerplateApiClient { - public call = createClient({ - endpointUrl: environmentOptions.serviceUrl, - requestInit: { - credentials: 'include', - mode: 'cors', - }, - }) + public call: ApiCall = jwtClient.call as ApiCall + public login = jwtClient.login + public logout = jwtClient.logout + public setTokens = jwtClient.setTokens + + public get isAuthenticated(): boolean { + return jwtClient.isAuthenticated + } } diff --git a/frontend/src/services/session.ts b/frontend/src/services/session.ts index 7b894b4c..fb96c83f 100644 --- a/frontend/src/services/session.ts +++ b/frontend/src/services/session.ts @@ -23,16 +23,17 @@ export class SessionService implements IdentityContext { private isInitialized = false - public async init() { + public async init(): Promise { await usingAsync(this.operation(), async () => { if (!this.isInitialized) { this.isInitialized = true try { - const { result } = await this.api.call({ method: 'GET', action: '/isAuthenticated' }) - this.state.setValue(result.isAuthenticated ? 'authenticated' : 'unauthenticated') - if (result.isAuthenticated) { + if (this.api.isAuthenticated) { const { result: usr } = await this.api.call({ method: 'GET', action: '/currentUser' }) this.currentUser.setValue(usr) + this.state.setValue('authenticated') + } else { + this.state.setValue('unauthenticated') } } catch (error) { this.state.setValue('offline') @@ -44,7 +45,8 @@ export class SessionService implements IdentityContext { public async login(username: string, password: string): Promise { await usingAsync(this.operation(), async () => { try { - const { result: usr } = await this.api.call({ method: 'POST', action: '/login', body: { username, password } }) + await this.api.login({ username, password }) + const { result: usr } = await this.api.call({ method: 'GET', action: '/currentUser' }) this.currentUser.setValue(usr) this.state.setValue('authenticated') this.notys.emit('onNotyAdded', { @@ -65,7 +67,7 @@ export class SessionService implements IdentityContext { public async logout(): Promise { return await usingAsync(this.operation(), async () => { - await this.api.call({ method: 'POST', action: '/logout' }) + await this.api.logout() this.currentUser.setValue(null) this.state.setValue('unauthenticated') this.notys.emit('onNotyAdded', { @@ -79,6 +81,7 @@ export class SessionService implements IdentityContext { public async isAuthenticated(): Promise { return this.state.getValue() === 'authenticated' } + public async isAuthorized(...roles: string[]): Promise { const currentUser = await this.getCurrentUser() for (const role of roles) { @@ -88,6 +91,7 @@ export class SessionService implements IdentityContext { } return true } + public async getCurrentUser(): Promise { const currentUser = this.currentUser.getValue() if (!currentUser) { diff --git a/package.json b/package.json index 4004c614..1583b853 100644 --- a/package.json +++ b/package.json @@ -17,22 +17,22 @@ "type": "module", "devDependencies": { "@eslint/js": "^10.0.1", - "@furystack/yarn-plugin-changelog": "^1.0.2", + "@furystack/yarn-plugin-changelog": "^1.0.4", "@playwright/test": "^1.58.2", - "@types/node": "^25.2.2", + "@types/node": "^25.3.1", "@vitest/coverage-v8": "^4.0.18", - "eslint": "^10.0.0", + "eslint": "^10.0.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "2.32.0", - "eslint-plugin-jsdoc": "^62.5.4", - "eslint-plugin-playwright": "^2.5.1", + "eslint-plugin-jsdoc": "^62.7.1", + "eslint-plugin-playwright": "^2.7.1", "eslint-plugin-prettier": "^5.5.5", "husky": "^9.1.7", "lint-staged": "^16.2.7", "prettier": "^3.8.1", - "rimraf": "^6.1.2", + "rimraf": "^6.1.3", "typescript": "^5.9.3", - "typescript-eslint": "^8.54.0", + "typescript-eslint": "^8.56.1", "vite": "^7.3.1", "vitest": "^4.0.18" }, diff --git a/service/package.json b/service/package.json index 7af51ffa..0877ac9d 100644 --- a/service/package.json +++ b/service/package.json @@ -12,18 +12,19 @@ "build": "tsc -b" }, "devDependencies": { - "@types/node": "^25.2.2", + "@types/node": "^25.3.1", "typescript": "^5.9.3", "vitest": "^4.0.18" }, "dependencies": { - "@furystack/core": "^15.0.35", - "@furystack/filesystem-store": "^7.0.35", - "@furystack/inject": "^12.0.29", - "@furystack/logging": "^8.0.29", - "@furystack/repository": "^10.0.35", - "@furystack/rest-service": "^11.0.3", - "@furystack/security": "^6.0.35", + "@furystack/auth-jwt": "^1.0.0", + "@furystack/core": "^15.2.1", + "@furystack/filesystem-store": "^7.0.39", + "@furystack/inject": "^12.0.30", + "@furystack/logging": "^8.0.30", + "@furystack/repository": "^10.1.2", + "@furystack/rest-service": "^12.0.0", + "@furystack/security": "^7.0.0", "common": "workspace:^" } } diff --git a/service/src/authorization/authorized-only.ts b/service/src/authorization/authorized-only.ts new file mode 100644 index 00000000..701cf51d --- /dev/null +++ b/service/src/authorization/authorized-only.ts @@ -0,0 +1,21 @@ +import { isAuthenticated } from '@furystack/core' +import type { Injector } from '@furystack/inject' +import type { AuthorizationResult } from '@furystack/repository' + +export const authorizedOnly = async (options: { injector: Injector }): Promise => { + const isAllowed = await isAuthenticated(options.injector) + return isAllowed + ? { isAllowed } + : { + isAllowed, + message: 'You are not authorized :(', + } +} + +export const authorizedDataSet = { + authorizeAdd: authorizedOnly, + authorizeGet: authorizedOnly, + authorizeRemove: authorizedOnly, + authorizeUpdate: authorizedOnly, + authorizeRemoveEntity: authorizedOnly, +} diff --git a/service/src/config.ts b/service/src/config.ts deleted file mode 100644 index 8045b307..00000000 --- a/service/src/config.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { addStore, InMemoryStore, isAuthenticated } from '@furystack/core' -import { FileSystemStore } from '@furystack/filesystem-store' -import { Injector } from '@furystack/inject' -import { useLogging, VerboseConsoleLogger } from '@furystack/logging' -import type { AuthorizationResult, DataSetSettings } from '@furystack/repository' -import { getRepository } from '@furystack/repository' -import { DefaultSession } from '@furystack/rest-service' -import { PasswordCredential, usePasswordPolicy } from '@furystack/security' -import { User } from 'common' -import { join } from 'path' - -export const authorizedOnly = async (options: { injector: Injector }): Promise => { - const isAllowed = await isAuthenticated(options.injector) - return isAllowed - ? { isAllowed } - : { - isAllowed, - message: 'You are not authorized :(', - } -} - -export const authorizedDataSet: Partial> = { - authorizeAdd: authorizedOnly, - authorizeGet: authorizedOnly, - authorizeRemove: authorizedOnly, - authorizeUpdate: authorizedOnly, - authorizeRemoveEntity: authorizedOnly, -} - -export const injector = new Injector() -useLogging(injector, VerboseConsoleLogger) -addStore( - injector, - new FileSystemStore({ - model: User, - primaryKey: 'username', - tickMs: 30 * 1000, - fileName: join(process.cwd(), 'users.json'), - }), -) - .addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' })) - .addStore( - new FileSystemStore({ - model: PasswordCredential, - primaryKey: 'userName', - fileName: join(process.cwd(), '..', '..', 'pwc.json'), - }), - ) - -getRepository(injector).createDataSet(User, 'username', { - ...authorizedDataSet, -}) - -usePasswordPolicy(injector) diff --git a/service/src/get-cors-options.ts b/service/src/get-cors-options.ts new file mode 100644 index 00000000..21c57604 --- /dev/null +++ b/service/src/get-cors-options.ts @@ -0,0 +1,10 @@ +import type { CorsOptions } from '@furystack/rest-service' + +/** + * @returns The CORS options to use service-wide + */ +export const getCorsOptions = (): CorsOptions => ({ + credentials: true, + origins: ['http://localhost:8080'], + headers: ['cache', 'content-type', 'authorization'], +}) diff --git a/service/src/get-port.ts b/service/src/get-port.ts new file mode 100644 index 00000000..d766085a --- /dev/null +++ b/service/src/get-port.ts @@ -0,0 +1,5 @@ +/** + * @param env The Env object, process.env by default + * @returns The port number to use + */ +export const getPort = (env = process.env): number => parseInt(env.APP_SERVICE_PORT as string, 10) || 9090 diff --git a/service/src/root-injector.ts b/service/src/root-injector.ts new file mode 100644 index 00000000..9a3918ad --- /dev/null +++ b/service/src/root-injector.ts @@ -0,0 +1,7 @@ +import { Injector } from '@furystack/inject' +import { useLogging, VerboseConsoleLogger } from '@furystack/logging' +import { attachShutdownHandler } from './shutdown-handler.js' + +export const injector = new Injector() +useLogging(injector, VerboseConsoleLogger) +void attachShutdownHandler(injector) diff --git a/service/src/seed.ts b/service/src/seed.ts index a4344a75..5bd5ea8b 100644 --- a/service/src/seed.ts +++ b/service/src/seed.ts @@ -1,39 +1,42 @@ -import type { FindOptions, PhysicalStore, WithOptionalId } from '@furystack/core' -import { StoreManager } from '@furystack/core' +import type { FindOptions, WithOptionalId } from '@furystack/core' +import { useSystemIdentityContext } from '@furystack/core' import type { Injector } from '@furystack/inject' import { getLogger } from '@furystack/logging' +import type { DataSet } from '@furystack/repository' +import { getDataSetFor } from '@furystack/repository' import { PasswordAuthenticator, PasswordCredential } from '@furystack/security' +import { usingAsync } from '@furystack/utils' import { User } from 'common' -import { injector } from './config.js' +import { injector } from './root-injector.js' +import { setupStore } from './setup-store.js' /** * gets an existing instance if exists or create and return if not. Throws error on multiple result * @param filter The filter term * @param instance The instance to be created if there is no instance present - * @param store The physical store to use + * @param dataSet The DataSet to use * @param i The Injector instance * @returns The retrieved or created object */ export const getOrCreate = async ( filter: FindOptions>, instance: WithOptionalId, - store: PhysicalStore, + dataSet: DataSet, i: Injector, ): Promise => { - const result = await store.find(filter) + const result = await dataSet.find(i, filter) const logger = getLogger(i).withScope('seeder') if (result.length === 1) { return result[0] } else if (result.length === 0) { await logger.verbose({ - message: `Entity of type '${store.model.name}' not exists, adding: '${JSON.stringify(filter)}'`, + message: `Entity not exists, adding: '${JSON.stringify(filter)}'`, }) - const createResult = await store.add(instance) + const createResult = await dataSet.add(i, instance) return createResult.created[0] } else { const message = `Seed filter contains '${result.length}' results for ${JSON.stringify(filter)}` await logger.warning({ message }) - // throw Error(message) return result[0] } } @@ -43,34 +46,36 @@ export const getOrCreate = async ( * @param i The injector instance */ export const seed = async (i: Injector): Promise => { - const logger = getLogger(i).withScope('seeder') - await logger.verbose({ message: 'Seeding data...' }) - const sm = i.getInstance(StoreManager) - const userStore = sm.getStoreFor(User, 'username') - const pwcStore = sm.getStoreFor(PasswordCredential, 'userName') - const cred = await i.getInstance(PasswordAuthenticator).hasher.createCredential('testuser', 'password') - await logger.verbose({ message: 'Saving credential...' }) - await getOrCreate( - { - filter: { userName: { $eq: 'testuser' } }, - }, - cred, - pwcStore, - i, - ) - await logger.verbose({ message: 'Saving User...' }) - await getOrCreate( - { filter: { username: { $eq: 'testuser' } } }, - { - username: 'testuser', - roles: [], - }, - userStore, - i, - ) + await usingAsync(useSystemIdentityContext({ injector: i, username: 'seeder' }), async (systemInjector) => { + const logger = getLogger(systemInjector).withScope('seeder') + await logger.verbose({ message: 'Seeding data...' }) + const userDataSet = getDataSetFor(systemInjector, User, 'username') + const pwcDataSet = getDataSetFor(systemInjector, PasswordCredential, 'userName') + const cred = await systemInjector.getInstance(PasswordAuthenticator).hasher.createCredential('testuser', 'password') + await logger.verbose({ message: 'Saving credential...' }) + await getOrCreate( + { + filter: { userName: { $eq: 'testuser' } }, + }, + cred, + pwcDataSet, + systemInjector, + ) + await logger.verbose({ message: 'Saving User...' }) + await getOrCreate( + { filter: { username: { $eq: 'testuser' } } }, + { + username: 'testuser', + roles: [], + }, + userDataSet, + systemInjector, + ) - await logger.verbose({ message: 'Seeding data completed.' }) + await logger.verbose({ message: 'Seeding data completed.' }) + }) } +setupStore(injector) await seed(injector) await injector[Symbol.asyncDispose]() diff --git a/service/src/service.ts b/service/src/service.ts index 7fc89c03..7baf7f4b 100644 --- a/service/src/service.ts +++ b/service/src/service.ts @@ -1,75 +1,10 @@ -import { - DefaultSession, - GetCurrentUser, - IsAuthenticated, - JsonResult, - LoginAction, - LogoutAction, - Validate, - useHttpAuthentication, - useRestService, - useStaticFiles, -} from '@furystack/rest-service' -import type { BoilerplateApi } from 'common' -import { User } from 'common' -import BoilerplateApiSchemas from 'common/schemas/boilerplate-api.json' with { type: 'json' } -import { injector } from './config.js' -import { attachShutdownHandler } from './shutdown-handler.js' +import { injector } from './root-injector.js' +import { setupStore } from './setup-store.js' +import { setupRestApi } from './setup-rest-api.js' -const port = parseInt(process.env.APP_SERVICE_PORT as string, 10) || 9090 +setupStore(injector) -useHttpAuthentication(injector, { - getUserStore: (sm) => sm.getStoreFor(User, 'username'), - getSessionStore: (sm) => sm.getStoreFor(DefaultSession, 'sessionId'), -}) -useRestService({ - injector, - root: 'api', - port, - name: 'Boilerplate Service', - version: '1.0.0', - description: 'API for Furystack Boilerplate Application containing simple authentication and example endpoints', - cors: { - credentials: true, - origins: ['http://localhost:8080'], - headers: ['cache', 'content-type'], - }, - api: { - GET: { - '/currentUser': GetCurrentUser, - '/isAuthenticated': IsAuthenticated, - '/testQuery': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestQueryEndpoint' })(async (options) => - JsonResult({ param1Value: options.getQuery().param1 }), - ), - '/testUrlParams/:urlParam': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestUrlParamsEndpoint' })( - async (options) => JsonResult({ urlParamValue: options.getUrlParams().urlParam }), - ), - }, - POST: { - '/login': LoginAction, - '/logout': LogoutAction, - '/testPostBody': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestPostBodyEndpoint' })( - async (options) => { - const body = await options.getBody() - return JsonResult({ bodyValue: body.value }) - }, - ), - }, - }, -}).catch((err) => { - console.error(err) - process.exit(1) -}) - -useStaticFiles({ - injector, - baseUrl: '/', - path: '../frontend/dist', - port, - fallback: 'index.html', -}).catch((err) => { +setupRestApi(injector).catch((err) => { console.error(err) process.exit(1) }) - -void attachShutdownHandler(injector) diff --git a/service/src/setup-rest-api.ts b/service/src/setup-rest-api.ts new file mode 100644 index 00000000..2010b6fa --- /dev/null +++ b/service/src/setup-rest-api.ts @@ -0,0 +1,67 @@ +import { JwtLoginAction, JwtLogoutAction, JwtRefreshAction } from '@furystack/auth-jwt' +import type { Injector } from '@furystack/inject' +import { + Authenticate, + GetCurrentUser, + IsAuthenticated, + JsonResult, + LoginAction, + LogoutAction, + Validate, + useRestService, + useStaticFiles, +} from '@furystack/rest-service' +import type { BoilerplateApi } from 'common' +import BoilerplateApiSchemas from 'common/schemas/boilerplate-api.json' with { type: 'json' } +import { getCorsOptions } from './get-cors-options.js' +import { getPort } from './get-port.js' + +export const setupRestApi = async (injector: Injector): Promise => { + const port = getPort() + + await useRestService({ + injector, + root: 'api', + port, + name: 'Boilerplate Service', + version: '1.0.0', + description: 'API for Furystack Boilerplate Application containing simple authentication and example endpoints', + cors: getCorsOptions(), + api: { + GET: { + '/currentUser': GetCurrentUser, + '/isAuthenticated': IsAuthenticated, + '/testQuery': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestQueryEndpoint' })(async (options) => + JsonResult({ param1Value: options.getQuery().param1 }), + ), + '/testUrlParams/:urlParam': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestUrlParamsEndpoint' })( + async (options) => JsonResult({ urlParamValue: options.getUrlParams().urlParam }), + ), + '/testAuthorized': Authenticate()(async () => + JsonResult({ message: 'Hello from authorized endpoint!', timestamp: new Date().toISOString() }), + ), + }, + POST: { + '/login': LoginAction, + '/logout': LogoutAction, + '/jwt/login': JwtLoginAction, + '/jwt/refresh': JwtRefreshAction, + '/jwt/logout': JwtLogoutAction, + '/testPostBody': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestPostBodyEndpoint' })( + async (options) => { + const body = await options.getBody() + return JsonResult({ bodyValue: body.value }) + }, + ), + }, + }, + }) + + await useStaticFiles({ + injector, + baseUrl: '/', + path: '../frontend/dist', + port, + fallback: 'index.html', + }) +} diff --git a/service/src/setup-store.ts b/service/src/setup-store.ts new file mode 100644 index 00000000..954001b6 --- /dev/null +++ b/service/src/setup-store.ts @@ -0,0 +1,46 @@ +import { RefreshToken, useJwtAuthentication } from '@furystack/auth-jwt' +import { addStore, InMemoryStore } from '@furystack/core' +import { FileSystemStore } from '@furystack/filesystem-store' +import type { Injector } from '@furystack/inject' +import { getRepository } from '@furystack/repository' +import { DefaultSession, useHttpAuthentication } from '@furystack/rest-service' +import { PasswordCredential, PasswordResetToken, usePasswordPolicy } from '@furystack/security' +import { User } from 'common' +import { join } from 'path' +import { authorizedDataSet } from './authorization/authorized-only.js' + +export const setupStore = (injector: Injector): void => { + addStore( + injector, + new FileSystemStore({ + model: User, + primaryKey: 'username', + tickMs: 30_000, + fileName: join(process.cwd(), 'users.json'), + }), + ) + .addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' })) + .addStore( + new FileSystemStore({ + model: PasswordCredential, + primaryKey: 'userName', + fileName: join(process.cwd(), '..', '..', 'pwc.json'), + }), + ) + .addStore(new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' })) + .addStore(new InMemoryStore({ model: RefreshToken, primaryKey: 'token' })) + + getRepository(injector) + .createDataSet(User, 'username', { ...authorizedDataSet }) + .createDataSet(DefaultSession, 'sessionId') + .createDataSet(PasswordCredential, 'userName') + .createDataSet(PasswordResetToken, 'token') + .createDataSet(RefreshToken, 'token') + + usePasswordPolicy(injector) + useHttpAuthentication(injector) + useJwtAuthentication(injector, { + secret: process.env.JWT_SECRET || 'change-me-to-a-secure-32-byte-secret!', + accessTokenExpirationSeconds: 60, + }) +} diff --git a/yarn.lock b/yarn.lock index 00d9fd49..e967f328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -504,14 +504,14 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.23.0": - version: 0.23.1 - resolution: "@eslint/config-array@npm:0.23.1" +"@eslint/config-array@npm:^0.23.2": + version: 0.23.2 + resolution: "@eslint/config-array@npm:0.23.2" dependencies: - "@eslint/object-schema": "npm:^3.0.1" + "@eslint/object-schema": "npm:^3.0.2" debug: "npm:^4.3.1" - minimatch: "npm:^10.1.1" - checksum: 10c0/9a676f3820b3c4dcea8053d07b22c8d8c2501c68d146d35a046e74f825de98deee3679b0cd980e0493a727c26efcb65cd508a96679402936c4ae86ab04a6c918 + minimatch: "npm:^10.2.1" + checksum: 10c0/95d7506c3fcb13c9a477f0fd501d552a4f136425fdf41a57058565d4730d888c78a467f8cefee92c7ac911b2c9da72629cb90507bc943cb2e5ae7bcdcdd2b759 languageName: node linkType: hard @@ -545,10 +545,10 @@ __metadata: languageName: node linkType: hard -"@eslint/object-schema@npm:^3.0.1": - version: 3.0.1 - resolution: "@eslint/object-schema@npm:3.0.1" - checksum: 10c0/96ddab8a2f5f1ae4203c8881b9c25a9177e27ca19cd609ea0c275e09d9a59ef0bbcb46e8ef59b887a9054933d96b23c70a98e652a77532273be9cce82f4e38e9 +"@eslint/object-schema@npm:^3.0.2": + version: 3.0.2 + resolution: "@eslint/object-schema@npm:3.0.2" + checksum: 10c0/5f8b2e264bbde6f7c86f6846a2f04cb6e3f52df49e3cce0659cea31d7f7410bb5ac681f6f910294f8362e427054665d2c5b5c794580cab6b0d5a1c177e131ec1 languageName: node linkType: hard @@ -569,148 +569,172 @@ __metadata: languageName: node linkType: hard -"@furystack/core@npm:^15.0.35": - version: 15.0.35 - resolution: "@furystack/core@npm:15.0.35" +"@furystack/auth-jwt@npm:^1.0.0": + version: 1.0.0 + resolution: "@furystack/auth-jwt@npm:1.0.0" dependencies: - "@furystack/inject": "npm:^12.0.29" - "@furystack/utils": "npm:^8.1.9" - checksum: 10c0/1fcfbcebfe614bf1ec69a57a7257759df4a2e53cd4f7616af12a80c2e49c3648be070cdc161c977babd9b3e907b71400da70cc38a95bf83ef82ee64385810584 + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/repository": "npm:^10.1.2" + "@furystack/rest": "npm:^8.0.39" + "@furystack/rest-client-fetch": "npm:^8.0.39" + "@furystack/rest-service": "npm:^12.0.0" + "@furystack/security": "npm:^7.0.0" + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/08a98808f024e04129ed1650dea9ec7e1d311d20a63ae8fe3fd7e886a2173ea85f1f852c786949e53959fad06d914ef04ce0c720b1c6f417afeb854124e73d3a languageName: node linkType: hard -"@furystack/filesystem-store@npm:^7.0.35": - version: 7.0.35 - resolution: "@furystack/filesystem-store@npm:7.0.35" +"@furystack/cache@npm:^6.0.0": + version: 6.0.0 + resolution: "@furystack/cache@npm:6.0.0" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/utils": "npm:^8.1.9" - semaphore-async-await: "npm:^1.5.1" - checksum: 10c0/7b3247fcb059819efcd6d365bf524715e6aa1df180afec740fb41707a05be812db64e527dc9fbf4aae9d154a79bac4928db5979e754dd3f39034231b2e6bc912 + "@furystack/inject": "npm:^12.0.30" + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/b3b9cd757215139a324a36879449866eb1739e9ff2d7dd5fc56e96eb72bab8ea5fe1b2028b94a89d1990eb85fa2e03bd940bf150a68463d6eebc71d797a858d5 languageName: node linkType: hard -"@furystack/inject@npm:^12.0.29": - version: 12.0.29 - resolution: "@furystack/inject@npm:12.0.29" +"@furystack/core@npm:^15.2.1": + version: 15.2.1 + resolution: "@furystack/core@npm:15.2.1" dependencies: - "@furystack/utils": "npm:^8.1.9" - checksum: 10c0/ca1b37830c5b8d13f45bd539842ba61a196381113bc51d3532497ba3b095a441705fdd5b0a33b6371a4c5242611713cec89de682fe55c278888da2f60ff139a8 + "@furystack/inject": "npm:^12.0.30" + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/9a4009e4be30b027bab4a40f5b906132fe67ad914b36e4c4942bc49bac6832a376e67d81096cd5348f92c3839bdc8dc5a1870f591414685acea85d005fe1e107 languageName: node linkType: hard -"@furystack/logging@npm:^8.0.29": - version: 8.0.29 - resolution: "@furystack/logging@npm:8.0.29" +"@furystack/filesystem-store@npm:^7.0.39": + version: 7.0.39 + resolution: "@furystack/filesystem-store@npm:7.0.39" dependencies: - "@furystack/inject": "npm:^12.0.29" - checksum: 10c0/eae7ddaaa27a99eb36c80b253397fb798581824a075cd1c5042d67c4ad7f4828387c563433e783da648de2023cc29382a74b08a1ffd07dc4a7f8534af2811100 + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/1ffdd2c24ebe44a3929581104e807c230921178cb83e9dae8a0716d167e633a47b9e8104cd07bdd62d46fce30b5ba1252c739f30581fe954b72401ff35e24bbd languageName: node linkType: hard -"@furystack/repository@npm:^10.0.35": - version: 10.0.35 - resolution: "@furystack/repository@npm:10.0.35" +"@furystack/inject@npm:^12.0.30": + version: 12.0.30 + resolution: "@furystack/inject@npm:12.0.30" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/utils": "npm:^8.1.9" - checksum: 10c0/ea26bfc69f52d67c7b9b06b9ee865f8fbb584687f3a694c541fd4b05d30348f87720bad5b02d4853f2fb33ebbbc78497ec231e7d6862151d28edc9e7ebd0d231 + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/4727a051686f97ae7bff1b01319c6e63477bd508c0d4e695b9bfdff6f42326a01584c6608265434af9ba5be8ea4f786645be2f8ffe5fd1d3a55fe9b8642db19c languageName: node linkType: hard -"@furystack/rest-client-fetch@npm:^8.0.35": - version: 8.0.35 - resolution: "@furystack/rest-client-fetch@npm:8.0.35" +"@furystack/logging@npm:^8.0.30": + version: 8.0.30 + resolution: "@furystack/logging@npm:8.0.30" dependencies: - "@furystack/rest": "npm:^8.0.35" - path-to-regexp: "npm:^8.3.0" - checksum: 10c0/5abfd1a12a6004d063ac4d6f5b658b341ba69263bc6644138571f0450d292361144f37e68944f2b877c905595bd90791c0e9353765c6365ab23b77140e6cf7e2 + "@furystack/inject": "npm:^12.0.30" + checksum: 10c0/edbf555f6e1eb91f5e32ecbc2eb49e3a7f089a419334ddb1f0d63de09882154d738957daa62ccc06eb3a86f81fd3fe7ab4b32be79043969b0ab15ffffd79af62 languageName: node linkType: hard -"@furystack/rest-service@npm:^11.0.3": - version: 11.0.3 - resolution: "@furystack/rest-service@npm:11.0.3" +"@furystack/repository@npm:^10.1.2": + version: 10.1.2 + resolution: "@furystack/repository@npm:10.1.2" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/repository": "npm:^10.0.35" - "@furystack/rest": "npm:^8.0.35" - "@furystack/security": "npm:^6.0.35" - "@furystack/utils": "npm:^8.1.9" - ajv: "npm:^8.17.1" + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/utils": "npm:^8.1.10" + checksum: 10c0/c1542b0b4a1f000392bd659c53d1fe6d8478fa6cdffc95e68a7ca5179900d9156e012241104504721899d05f4d9083538ee3ca9d709ba12d9f0d736492e660e1 + languageName: node + linkType: hard + +"@furystack/rest-client-fetch@npm:^8.0.39": + version: 8.0.39 + resolution: "@furystack/rest-client-fetch@npm:8.0.39" + dependencies: + "@furystack/rest": "npm:^8.0.39" + path-to-regexp: "npm:^8.3.0" + checksum: 10c0/c238dfd13ef43e528c8792fd062357527ad152d3015256cd64b9219eaaaa91e63d8fe9bdcd7f0c6c06f777e79af403bc211a85eaf95841c28927f10a17d69d22 + languageName: node + linkType: hard + +"@furystack/rest-service@npm:^12.0.0": + version: 12.0.0 + resolution: "@furystack/rest-service@npm:12.0.0" + dependencies: + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/repository": "npm:^10.1.2" + "@furystack/rest": "npm:^8.0.39" + "@furystack/security": "npm:^7.0.0" + "@furystack/utils": "npm:^8.1.10" + ajv: "npm:^8.18.0" ajv-formats: "npm:^3.0.1" path-to-regexp: "npm:^8.3.0" - semaphore-async-await: "npm:^1.5.1" - checksum: 10c0/511a198bf43bbac9eed975fb5c4b147c2112c52d1462bde8ea803837375e4a2453b54022c5151d130eb286d4dbd96f537b365b91dcfcb66911006cde15f0f66e + checksum: 10c0/3c76d369dd7153488e25b29abd169b5a1a76e0e8c01c00c0a5f7a6e47b626063d42e6cebfe323e4a6acf27ca8575ad2c87e31b222b2e13edc70cfaec00fb106f languageName: node linkType: hard -"@furystack/rest@npm:^8.0.35": - version: 8.0.35 - resolution: "@furystack/rest@npm:8.0.35" +"@furystack/rest@npm:^8.0.39": + version: 8.0.39 + resolution: "@furystack/rest@npm:8.0.39" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - checksum: 10c0/60a439695821f39e3e28ac906502a5fe4eacaab7c4ee4523a6ca4434fbb78f578453a938b8eb5f5ae8f1dc7257705e4c915ed5b43c7d4287c72c47bc6cbd927a + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + checksum: 10c0/fb2878d8bd4a44b15496d9598dc9fc9cfdaa54b86bdb9b5454988dcb9a27983433d7f4e9e7c8eee9cef10f79d9d82cbbdaa78bc4851776b1c9af155fac0e426a languageName: node linkType: hard -"@furystack/security@npm:^6.0.35": - version: 6.0.35 - resolution: "@furystack/security@npm:6.0.35" +"@furystack/security@npm:^7.0.0": + version: 7.0.0 + resolution: "@furystack/security@npm:7.0.0" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - checksum: 10c0/6b93a42e90092d22935bd2f54e2c06f82c4c4e490dbb9212d6a140f2f86c6a906684ab308cd8dfb82a11dd91115f64f4e8b5ec7e7cc547b9b185d3b4629d3717 + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/repository": "npm:^10.1.2" + checksum: 10c0/8164354399b26236497b0d584896f758ed0fdd5b21689175e788ad65bc89c3bb517f775b6d327fa9a83d6988a1f8bdb128d981636b1414f4dba413a9604cf0e2 languageName: node linkType: hard -"@furystack/shades-common-components@npm:^12.0.0": - version: 12.0.0 - resolution: "@furystack/shades-common-components@npm:12.0.0" +"@furystack/shades-common-components@npm:^13.0.0": + version: 13.0.0 + resolution: "@furystack/shades-common-components@npm:13.0.0" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/shades": "npm:^12.0.0" - "@furystack/utils": "npm:^8.1.9" + "@furystack/cache": "npm:^6.0.0" + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/shades": "npm:^12.2.3" + "@furystack/utils": "npm:^8.1.10" path-to-regexp: "npm:^8.3.0" - semaphore-async-await: "npm:^1.5.1" - checksum: 10c0/30e8e8dd57c8b673f17e0b86f70ab3e74da11c45bec106c3446dd51e0e07ecce4316dd47f3bfd8036c810e80805f3557658c9f13bab407a56d5d39214673444c + checksum: 10c0/95908c5c2dd5469da2620c74192656db789fefba9027019d6c509593eaaf1c520ba496dbf932c7c6f94144e3d11b91a3cc37569ee04912257869f29abb3468fa languageName: node linkType: hard -"@furystack/shades@npm:^12.0.0": - version: 12.0.0 - resolution: "@furystack/shades@npm:12.0.0" +"@furystack/shades@npm:^12.2.3": + version: 12.2.3 + resolution: "@furystack/shades@npm:12.2.3" dependencies: - "@furystack/inject": "npm:^12.0.29" - "@furystack/rest": "npm:^8.0.35" - "@furystack/utils": "npm:^8.1.9" + "@furystack/inject": "npm:^12.0.30" + "@furystack/rest": "npm:^8.0.39" + "@furystack/utils": "npm:^8.1.10" path-to-regexp: "npm:^8.3.0" - semaphore-async-await: "npm:^1.5.1" - checksum: 10c0/43c0643e36231852715be60da39cb6127cc6736728ee187985a8fb970c7ea27b1588edb96a93ae9106b3aac8655f9a2637f911744d27c46205e44ce9d8a7f6c4 + checksum: 10c0/b23064f450f9a4b3003d3700f5efae248d29171db3d6d51f9fb45256ae4a01a418ba24ff94d7a0a460b5d79cb837f567dc478c4dc56078241d7585ce0ba71490 languageName: node linkType: hard -"@furystack/utils@npm:^8.1.9": - version: 8.1.9 - resolution: "@furystack/utils@npm:8.1.9" - checksum: 10c0/a95bac6cf454f997f2a4ef4e7b945bcbc215110d8a40d78aaf1a75e0b42ff6e78c4506c35a1faa0c5a996ad31052c2abc9b4da780d3438ff69804259e830e311 +"@furystack/utils@npm:^8.1.10": + version: 8.1.10 + resolution: "@furystack/utils@npm:8.1.10" + checksum: 10c0/318768fbdb079a0585dac25e48608dcc302248c616c59806446e3b025d11524fc4e2615c1a972d091cf8d3f579914fa8f87f4d1e883368119058544a4e36ba33 languageName: node linkType: hard -"@furystack/yarn-plugin-changelog@npm:^1.0.2": - version: 1.0.2 - resolution: "@furystack/yarn-plugin-changelog@npm:1.0.2" +"@furystack/yarn-plugin-changelog@npm:^1.0.4": + version: 1.0.4 + resolution: "@furystack/yarn-plugin-changelog@npm:1.0.4" dependencies: "@yarnpkg/cli": "npm:^4.12.0" "@yarnpkg/core": "npm:^4.5.0" "@yarnpkg/fslib": "npm:^3.1.4" clipanion: "npm:^4.0.0-rc.4" - checksum: 10c0/09a50571fe62b0fbf994f8df41fe7939c693376d49d4bffc66aae775bc21490c9e042bbac2bb81844a740386bb44cfb6769d797b20f8d52571c5c72af1a93255 + checksum: 10c0/1d414bdcd9ed3e7101c797c6130e526a577262e34b83085bd572ef8245695a52f02cdbf10b42914806c4228d2f46ee9f1be9eeb9b59bb8e7a5fca80a54b6459f languageName: node linkType: hard @@ -1391,7 +1415,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^25.2.2": +"@types/node@npm:*": version: 25.2.2 resolution: "@types/node@npm:25.2.2" dependencies: @@ -1409,6 +1433,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^25.3.1": + version: 25.3.1 + resolution: "@types/node@npm:25.3.1" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/f4714e3f2c393434cdefbf207f3fe470bcf4d6c5717298c3fc9239e362a57cd25e59603c60e9c956e7d5d0dfb97cf2e037b37cf6dc2f3876f6a963146f7adc6d + languageName: node + linkType: hard + "@types/responselike@npm:^1.0.0": version: 1.0.3 resolution: "@types/responselike@npm:1.0.3" @@ -1439,138 +1472,145 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.54.0" +"@typescript-eslint/eslint-plugin@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.56.1" dependencies: "@eslint-community/regexpp": "npm:^4.12.2" - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/type-utils": "npm:8.54.0" - "@typescript-eslint/utils": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.56.1" + "@typescript-eslint/type-utils": "npm:8.56.1" + "@typescript-eslint/utils": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" ignore: "npm:^7.0.5" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.4.0" peerDependencies: - "@typescript-eslint/parser": ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 + "@typescript-eslint/parser": ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e533c8285880b883e02a833f378597c2776e6b0c20a5935440e2a02c1c42f40069a8badcf6d581bb4ec35a6856a806c4b66674c1c15c33cd64cc6b9c0cdd1dad + checksum: 10c0/8a97e777792ee3e25078884ba0a04f6732367779c9487abcdc5a2d65b224515fa6a0cf1fac1aafc52fb30f3af97f2e1c9949aadbd6ca74a0165691f95494a721 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/parser@npm:8.54.0" +"@typescript-eslint/parser@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/parser@npm:8.56.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/typescript-estree": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" debug: "npm:^4.4.3" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/60a1cfe94bc23086f03701640f4d83d7e37b8f4d729011e0f029e5accf2b3d099c50938c0a798a399e86046279432ff663f33102ba4338c4c82f7acead2bcbac + checksum: 10c0/61c9dab481e795b01835c00c9c7c845f1d7ea7faf3b8657fccee0f8658a65390cb5fe2b5230ae8c4241bd6e0c32aa9455a91989a492bd3bd6fec7c7d9339377a languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/project-service@npm:8.54.0" +"@typescript-eslint/project-service@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/project-service@npm:8.56.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.54.0" - "@typescript-eslint/types": "npm:^8.54.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.56.1" + "@typescript-eslint/types": "npm:^8.56.1" debug: "npm:^4.4.3" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/3392ae259199021a80616a44d9484d1c363f61bc5c631dff2d08c6a906c98716a20caa7b832b8970120a1eb1eb2de3ee890cd527d6edb04f532f4e48a690a792 + checksum: 10c0/ca61cde575233bc79046d73ddd330d183fb3cbb941fddc31919336317cda39885c59296e2e5401b03d9325a64a629e842fd66865705ff0d85d83ee3ee40871e8 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/scope-manager@npm:8.54.0" +"@typescript-eslint/scope-manager@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/scope-manager@npm:8.56.1" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" - checksum: 10c0/794740a5c0c1afc38d71e6bc59cc62870286e40d99f15e9760e76fb3d4197e961ee151c286c428535c404f5137721242a14da21350b749d0feb1f589f167814f + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" + checksum: 10c0/89cc1af2635eee23f2aa2ff87c08f88f3ad972ebf67eaacdc604a4ef4178535682bad73fd086e6f3c542e4e5d874253349af10d58291d079cc29c6c7e9831de4 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.54.0, @typescript-eslint/tsconfig-utils@npm:^8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0" +"@typescript-eslint/tsconfig-utils@npm:8.56.1, @typescript-eslint/tsconfig-utils@npm:^8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.1" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e8598b0f051650c085d749002138d12249a3efd03e7de02e9e7913939dddd649d159b91f29ca3d28f5ee798b3f528a7195688e23c5e0b315d534e7af20a0c99a + checksum: 10c0/d03b64d7ff19020beeefa493ae667c2e67a4547d25a3ecb9210a3a52afe980c093d772a91014bae699ee148bfb60cc659479e02bfc2946ea06954a8478ef1fe1 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/type-utils@npm:8.54.0" +"@typescript-eslint/type-utils@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/type-utils@npm:8.56.1" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" - "@typescript-eslint/utils": "npm:8.54.0" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/typescript-estree": "npm:8.56.1" + "@typescript-eslint/utils": "npm:8.56.1" debug: "npm:^4.4.3" ts-api-utils: "npm:^2.4.0" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/ad807800d8b2662f823505249a84a6f5b1246b192a7ff08c49f298e220e4d9bb3d76f1f0852510421e030161604a4b939bff87f11b9074f118a3bd1d26139c6f + checksum: 10c0/66517aed5059ef4a29605d06a510582f934d5789ae40ad673f1f0421f8aa13ec9ba7b8caab57ae9f270afacbf13ec5359cedfe74f21ae77e9a2364929f7e7cee languageName: node linkType: hard -"@typescript-eslint/types@npm:8.54.0, @typescript-eslint/types@npm:^8.54.0": +"@typescript-eslint/types@npm:8.56.1, @typescript-eslint/types@npm:^8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/types@npm:8.56.1" + checksum: 10c0/e5a0318abddf0c4f98da3039cb10b3c0601c8601f7a9f7043630f0d622dabfe83a4cd833545ad3531fc846e46ca2874377277b392c2490dffec279d9242d827b + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:^8.54.0": version: 8.54.0 resolution: "@typescript-eslint/types@npm:8.54.0" checksum: 10c0/2219594fe5e8931ff91fd1b7a2606d33cd4f093d43f9ca71bcaa37f106ef79ad51f830dea51392f7e3d8bca77f7077ef98733f87bc008fad2f0bbd9ea5fb8a40 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.54.0" +"@typescript-eslint/typescript-estree@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.56.1" dependencies: - "@typescript-eslint/project-service": "npm:8.54.0" - "@typescript-eslint/tsconfig-utils": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/visitor-keys": "npm:8.54.0" + "@typescript-eslint/project-service": "npm:8.56.1" + "@typescript-eslint/tsconfig-utils": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/visitor-keys": "npm:8.56.1" debug: "npm:^4.4.3" - minimatch: "npm:^9.0.5" + minimatch: "npm:^10.2.2" semver: "npm:^7.7.3" tinyglobby: "npm:^0.2.15" ts-api-utils: "npm:^2.4.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/1a1a7c0a318e71f3547ab5573198d36165ea152c50447ef92e6326303f9a5c397606201ba80c7b86a725dcdd2913e924be94466a0c33b1b0c3ee852059e646b6 + checksum: 10c0/92f4421dac41be289761200dc2ed85974fa451deacb09490ae1870a25b71b97218e609a90d4addba9ded5b2abdebc265c9db7f6e9ce6d29ed20e89b8487e9618 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/utils@npm:8.54.0" +"@typescript-eslint/utils@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/utils@npm:8.56.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.9.1" - "@typescript-eslint/scope-manager": "npm:8.54.0" - "@typescript-eslint/types": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" + "@typescript-eslint/scope-manager": "npm:8.56.1" + "@typescript-eslint/types": "npm:8.56.1" + "@typescript-eslint/typescript-estree": "npm:8.56.1" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/949a97dca8024d39666e04ecdf2d4e12722f5064c387901e72bdcc7adafb96cf650a070dc79f9dd46fa1aae6ac2b5eac5ae3fe5a6979385208c28809a1bd143f + checksum: 10c0/d9ffd9b2944a2c425e0532f71dc61e61d0a923d1a17733cf2777c2a4ae638307d12d44f63b33b6b3dc62f02f47db93ec49344ecefe17b76ee3e4fb0833325be3 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.54.0": - version: 8.54.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.54.0" +"@typescript-eslint/visitor-keys@npm:8.56.1": + version: 8.56.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.56.1" dependencies: - "@typescript-eslint/types": "npm:8.54.0" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/f83a9aa92f7f4d1fdb12cbca28c6f5704c36371264606b456388b2c869fc61e73c86d3736556e1bb6e253f3a607128b5b1bf6c68395800ca06f18705576faadd + "@typescript-eslint/types": "npm:8.56.1" + eslint-visitor-keys: "npm:^5.0.0" + checksum: 10c0/86d97905dec1af964cc177c185933d040449acf6006096497f2e0093c6a53eb92b3ac1db9eb40a5a2e8d91160f558c9734331a9280797f09f284c38978b22190 languageName: node linkType: hard @@ -2282,6 +2322,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.16.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e + languageName: node + linkType: hard + "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" @@ -2303,19 +2352,19 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" +"ajv@npm:^6.14.0": + version: 6.14.0 + resolution: "ajv@npm:6.14.0" dependencies: fast-deep-equal: "npm:^3.1.1" fast-json-stable-stringify: "npm:^2.0.0" json-schema-traverse: "npm:^0.4.1" uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + checksum: 10c0/a2bc39b0555dc9802c899f86990eb8eed6e366cddbf65be43d5aa7e4f3c4e1a199d5460fd7ca4fb3d864000dbbc049253b72faa83b3b30e641ca52cb29a68c22 languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.17.1": +"ajv@npm:^8.0.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -2327,6 +2376,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:^8.18.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f + languageName: node + linkType: hard + "algoliasearch@npm:^4.2.0": version: 4.25.3 resolution: "algoliasearch@npm:4.25.3" @@ -2563,6 +2624,13 @@ __metadata: languageName: node linkType: hard +"balanced-match@npm:^4.0.2": + version: 4.0.4 + resolution: "balanced-match@npm:4.0.4" + checksum: 10c0/07e86102a3eb2ee2a6a1a89164f29d0dbaebd28f2ca3f5ca786f36b8b23d9e417eb3be45a4acf754f837be5ac0a2317de90d3fcb7f4f4dc95720a1f36b26a17b + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -2607,6 +2675,15 @@ __metadata: languageName: node linkType: hard +"brace-expansion@npm:^5.0.2": + version: 5.0.3 + resolution: "brace-expansion@npm:5.0.3" + dependencies: + balanced-match: "npm:^4.0.2" + checksum: 10c0/e474d300e581ec56851b3863ff1cf18573170c6d06deb199ccbd03b2119c36975f6ce2abc7b770f5bebddc1ab022661a9fea9b4d56f33315d7bef54d8793869e + languageName: node + linkType: hard + "braces@npm:^3.0.3": version: 3.0.3 resolution: "braces@npm:3.0.3" @@ -2900,8 +2977,8 @@ __metadata: version: 0.0.0-use.local resolution: "common@workspace:common" dependencies: - "@furystack/rest": "npm:^8.0.35" - "@types/node": "npm:^25.2.2" + "@furystack/rest": "npm:^8.0.39" + "@types/node": "npm:^25.3.1" ts-json-schema-generator: "npm:^2.5.0" vitest: "npm:^4.0.18" languageName: unknown @@ -3451,9 +3528,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^62.5.4": - version: 62.5.4 - resolution: "eslint-plugin-jsdoc@npm:62.5.4" +"eslint-plugin-jsdoc@npm:^62.7.1": + version: 62.7.1 + resolution: "eslint-plugin-jsdoc@npm:62.7.1" dependencies: "@es-joy/jsdoccomment": "npm:~0.84.0" "@es-joy/resolve.exports": "npm:1.2.0" @@ -3466,23 +3543,23 @@ __metadata: html-entities: "npm:^2.6.0" object-deep-merge: "npm:^2.0.0" parse-imports-exports: "npm:^0.2.4" - semver: "npm:^7.7.3" + semver: "npm:^7.7.4" spdx-expression-parse: "npm:^4.0.0" to-valid-identifier: "npm:^1.0.0" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/576a3dd2279c09ec579cbb944afed78029d5525a18dbef37097d510a0241c3792a597c440e80a299240772d49a6d0624bc90ffec32a72fca1e806e0554ac2119 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/949c1f11ed86ddac0903ffe65e5e30d36766badea2e42ceeaf85168ec6f540f94a9974896600410ce1fcbbf34809b39236cc9614411c15d05b93be166c21ec3c languageName: node linkType: hard -"eslint-plugin-playwright@npm:^2.5.1": - version: 2.5.1 - resolution: "eslint-plugin-playwright@npm:2.5.1" +"eslint-plugin-playwright@npm:^2.7.1": + version: 2.7.1 + resolution: "eslint-plugin-playwright@npm:2.7.1" dependencies: - globals: "npm:^16.4.0" + globals: "npm:^17.3.0" peerDependencies: eslint: ">=8.40.0" - checksum: 10c0/b8b752f8692b20b062f218be344c25ad366192b15e4b5c764e25b67a1fa6cfaffb67456aadd6fce5bc8ac7b0922a4413b76be35c7121a3c76e2fc09d09071f53 + checksum: 10c0/efd45122b4d4e77608862f0f25a95200a0e367abbe6d53bdce38c59009c4b90edcf12b6f1c2b50e86b0addea56c68ad0c7767768b0a2a73d7f09a814b827df72 languageName: node linkType: hard @@ -3506,15 +3583,15 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^9.1.0": - version: 9.1.0 - resolution: "eslint-scope@npm:9.1.0" +"eslint-scope@npm:^9.1.1": + version: 9.1.1 + resolution: "eslint-scope@npm:9.1.1" dependencies: "@types/esrecurse": "npm:^4.3.1" "@types/estree": "npm:^1.0.8" esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10c0/b503f739bb1d8da2e94b56b7655aaaa3af35e3180b93310523b11d326b90c4caf00ec0138a601c56f672a4da17958cf28d0c76806e448e5d35429754d2691040 + checksum: 10c0/58327b26cd6a78693951668ce68c466a535259173d187cbd5c9d3cbe657cfd5dfaf1c01ec3176b8f6f1cf240b48d01d01e0f76ad9300682d9dd51d5d1514d4c1 languageName: node linkType: hard @@ -3525,13 +3602,6 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-visitor-keys@npm:4.2.1" - checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 - languageName: node - linkType: hard - "eslint-visitor-keys@npm:^5.0.0": version: 5.0.0 resolution: "eslint-visitor-keys@npm:5.0.0" @@ -3539,13 +3609,20 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^10.0.0": - version: 10.0.0 - resolution: "eslint@npm:10.0.0" +"eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 + languageName: node + linkType: hard + +"eslint@npm:^10.0.2": + version: 10.0.2 + resolution: "eslint@npm:10.0.2" dependencies: "@eslint-community/eslint-utils": "npm:^4.8.0" "@eslint-community/regexpp": "npm:^4.12.2" - "@eslint/config-array": "npm:^0.23.0" + "@eslint/config-array": "npm:^0.23.2" "@eslint/config-helpers": "npm:^0.5.2" "@eslint/core": "npm:^1.1.0" "@eslint/plugin-kit": "npm:^0.6.0" @@ -3553,13 +3630,13 @@ __metadata: "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" "@types/estree": "npm:^1.0.6" - ajv: "npm:^6.12.4" + ajv: "npm:^6.14.0" cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^9.1.0" - eslint-visitor-keys: "npm:^5.0.0" - espree: "npm:^11.1.0" + eslint-scope: "npm:^9.1.1" + eslint-visitor-keys: "npm:^5.0.1" + espree: "npm:^11.1.1" esquery: "npm:^1.7.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -3570,7 +3647,7 @@ __metadata: imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" json-stable-stringify-without-jsonify: "npm:^1.0.1" - minimatch: "npm:^10.1.1" + minimatch: "npm:^10.2.1" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" peerDependencies: @@ -3580,7 +3657,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/87f3aa069693969841d773423c214ec83226873ead8565a65bdb40a7a0d3d5c95b8262c8232403eea235c5e1477457f893a3b6a72a0f4abc6bf2fee8f8410ef8 + checksum: 10c0/ed1aa142a1fa9c82fb30f0ee5b257d8e7655b601d2480ba29454184f7e2ea4b29c83adef1ac1e0f7a194fbd8b8f994e20f73e2c96bad8cb150d012425cdc3236 languageName: node linkType: hard @@ -3595,6 +3672,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^11.1.1": + version: 11.1.1 + resolution: "espree@npm:11.1.1" + dependencies: + acorn: "npm:^8.16.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/2feae74efdfb037b9e9fcb30506799845cf20900de5e441ed03e5c51aaa249f85ea5818ff177682acc0c9bfb4ac97e1965c238ee44ac7c305aab8747177bab69 + languageName: node + linkType: hard + "esprima@npm:^4.0.0": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -3805,14 +3893,15 @@ __metadata: resolution: "frontend@workspace:frontend" dependencies: "@codecov/vite-plugin": "npm:^1.9.1" - "@furystack/core": "npm:^15.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/logging": "npm:^8.0.29" - "@furystack/rest-client-fetch": "npm:^8.0.35" - "@furystack/shades": "npm:^12.0.0" - "@furystack/shades-common-components": "npm:^12.0.0" - "@furystack/utils": "npm:^8.1.9" - "@types/node": "npm:^25.2.2" + "@furystack/auth-jwt": "npm:^1.0.0" + "@furystack/core": "npm:^15.2.1" + "@furystack/inject": "npm:^12.0.30" + "@furystack/logging": "npm:^8.0.30" + "@furystack/rest-client-fetch": "npm:^8.0.39" + "@furystack/shades": "npm:^12.2.3" + "@furystack/shades-common-components": "npm:^13.0.0" + "@furystack/utils": "npm:^8.1.10" + "@types/node": "npm:^25.3.1" common: "workspace:^" typescript: "npm:^5.9.3" vite: "npm:^7.3.1" @@ -3916,22 +4005,22 @@ __metadata: resolution: "furystack-boilerplate-app@workspace:." dependencies: "@eslint/js": "npm:^10.0.1" - "@furystack/yarn-plugin-changelog": "npm:^1.0.2" + "@furystack/yarn-plugin-changelog": "npm:^1.0.4" "@playwright/test": "npm:^1.58.2" - "@types/node": "npm:^25.2.2" + "@types/node": "npm:^25.3.1" "@vitest/coverage-v8": "npm:^4.0.18" - eslint: "npm:^10.0.0" + eslint: "npm:^10.0.2" eslint-config-prettier: "npm:^10.1.8" eslint-plugin-import: "npm:2.32.0" - eslint-plugin-jsdoc: "npm:^62.5.4" - eslint-plugin-playwright: "npm:^2.5.1" + eslint-plugin-jsdoc: "npm:^62.7.1" + eslint-plugin-playwright: "npm:^2.7.1" eslint-plugin-prettier: "npm:^5.5.5" husky: "npm:^9.1.7" lint-staged: "npm:^16.2.7" prettier: "npm:^3.8.1" - rimraf: "npm:^6.1.2" + rimraf: "npm:^6.1.3" typescript: "npm:^5.9.3" - typescript-eslint: "npm:^8.54.0" + typescript-eslint: "npm:^8.56.1" vite: "npm:^7.3.1" vitest: "npm:^4.0.18" languageName: unknown @@ -4066,10 +4155,21 @@ __metadata: languageName: node linkType: hard -"globals@npm:^16.4.0": - version: 16.5.0 - resolution: "globals@npm:16.5.0" - checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 +"glob@npm:^13.0.3": + version: 13.0.6 + resolution: "glob@npm:13.0.6" + dependencies: + minimatch: "npm:^10.2.2" + minipass: "npm:^7.1.3" + path-scurry: "npm:^2.0.2" + checksum: 10c0/269c236f11a9b50357fe7a8c6aadac667e01deb5242b19c84975628f05f4438d8ee1354bb62c5d6c10f37fd59911b54d7799730633a2786660d8c69f1d18120a + languageName: node + linkType: hard + +"globals@npm:^17.3.0": + version: 17.3.0 + resolution: "globals@npm:17.3.0" + checksum: 10c0/7f21443ecaa60c6e9ff56d9fb6f10a9b5f9514e7f22e5392f715472bb220ce31c865ebf414a32695150e733fb3e1013e6322dbce70fddd1e066f372b8d55a4b8 languageName: node linkType: hard @@ -5029,7 +5129,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.1.1, minimatch@npm:^10.1.2": +"minimatch@npm:^10.1.2": version: 10.1.2 resolution: "minimatch@npm:10.1.2" dependencies: @@ -5038,6 +5138,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.2.1, minimatch@npm:^10.2.2": + version: 10.2.4 + resolution: "minimatch@npm:10.2.4" + dependencies: + brace-expansion: "npm:^5.0.2" + checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945 + languageName: node + linkType: hard + "minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -5161,6 +5270,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^7.1.3": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + "minizlib@npm:^2.1.1": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -5551,6 +5667,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.2": + version: 2.0.2 + resolution: "path-scurry@npm:2.0.2" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/b35ad37cf6557a87fd057121ce2be7695380c9138d93e87ae928609da259ea0a170fac6f3ef1eb3ece8a068e8b7f2f3adf5bb2374cf4d4a57fe484954fcc9482 + languageName: node + linkType: hard + "path-to-regexp@npm:^8.3.0": version: 8.3.0 resolution: "path-to-regexp@npm:8.3.0" @@ -5915,15 +6041,15 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^6.1.2": - version: 6.1.2 - resolution: "rimraf@npm:6.1.2" +"rimraf@npm:^6.1.3": + version: 6.1.3 + resolution: "rimraf@npm:6.1.3" dependencies: - glob: "npm:^13.0.0" + glob: "npm:^13.0.3" package-json-from-dist: "npm:^1.0.1" bin: rimraf: dist/esm/bin.mjs - checksum: 10c0/c11a6a6fad937ada03c12fe688860690df8296d7cd08dbe59e3cc087f44e43573ae26ecbe48e54cb7a6db745b8c81fe5a15b9359233cc21d52d9b5b3330fcc74 + checksum: 10c0/4a56537850102e20ba5d5eb49f366b4b7b2435389734b4b8480cf0e0eb0f6f5d0c44120a171aeb0d8f9ab40312a10d2262f3f50acbad803e32caef61b6cf86fc languageName: node linkType: hard @@ -6091,13 +6217,6 @@ __metadata: languageName: node linkType: hard -"semaphore-async-await@npm:^1.5.1": - version: 1.5.1 - resolution: "semaphore-async-await@npm:1.5.1" - checksum: 10c0/b5cc7bcbe755fa73d414b6ebabd9b73cec9193988ecb14b489753287acef77f4cf4c4eaa9c2cd942f24ec8e230d26116788c7405b256c39111b75c81e953a92f - languageName: node - linkType: hard - "semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -6107,7 +6226,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.2, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3": +"semver@npm:^7.1.2, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3, semver@npm:^7.7.4": version: 7.7.4 resolution: "semver@npm:7.7.4" bin: @@ -6120,14 +6239,15 @@ __metadata: version: 0.0.0-use.local resolution: "service@workspace:service" dependencies: - "@furystack/core": "npm:^15.0.35" - "@furystack/filesystem-store": "npm:^7.0.35" - "@furystack/inject": "npm:^12.0.29" - "@furystack/logging": "npm:^8.0.29" - "@furystack/repository": "npm:^10.0.35" - "@furystack/rest-service": "npm:^11.0.3" - "@furystack/security": "npm:^6.0.35" - "@types/node": "npm:^25.2.2" + "@furystack/auth-jwt": "npm:^1.0.0" + "@furystack/core": "npm:^15.2.1" + "@furystack/filesystem-store": "npm:^7.0.39" + "@furystack/inject": "npm:^12.0.30" + "@furystack/logging": "npm:^8.0.30" + "@furystack/repository": "npm:^10.1.2" + "@furystack/rest-service": "npm:^12.0.0" + "@furystack/security": "npm:^7.0.0" + "@types/node": "npm:^25.3.1" common: "workspace:^" typescript: "npm:^5.9.3" vitest: "npm:^4.0.18" @@ -6839,18 +6959,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.54.0": - version: 8.54.0 - resolution: "typescript-eslint@npm:8.54.0" +"typescript-eslint@npm:^8.56.1": + version: 8.56.1 + resolution: "typescript-eslint@npm:8.56.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.54.0" - "@typescript-eslint/parser": "npm:8.54.0" - "@typescript-eslint/typescript-estree": "npm:8.54.0" - "@typescript-eslint/utils": "npm:8.54.0" + "@typescript-eslint/eslint-plugin": "npm:8.56.1" + "@typescript-eslint/parser": "npm:8.56.1" + "@typescript-eslint/typescript-estree": "npm:8.56.1" + "@typescript-eslint/utils": "npm:8.56.1" peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/0ba92aa22c0aa10c88b0f4732950ed64245947f1c4ac17328dff94b43eaeddd3068595788725781fba07a87cc964304a075b3e37f9a86312173498fcc6ab4338 + checksum: 10c0/c33aeb9a8beab54308412dcd460ab60f845fee30eaed1fdc1083ff53c430a4dcbdfeac862136a21fb3a639538f8712d933fc410680c2a650e67b992720a0d9f6 languageName: node linkType: hard @@ -6900,6 +7020,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + "undici@npm:^5.25.4, undici@npm:^5.28.5": version: 5.29.0 resolution: "undici@npm:5.29.0" From ce72edbfd0bc04ec5b04aea6a5bb43785c31fbf7 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Thu, 26 Feb 2026 18:34:38 +0100 Subject: [PATCH 2/5] JWT and refactoring --- common/package.json | 2 +- common/schemas/boilerplate-api.json | 214 ++++++++++++-- common/schemas/jwt-api.json | 265 ++++++++++++++++++ common/src/bin/create-schemas.ts | 5 + common/src/boilerplate-api.ts | 7 + common/src/index.ts | 1 + common/src/jwt-api.ts | 12 + frontend/package.json | 10 +- .../src/services/boilerplate-api-client.ts | 47 +++- package.json | 2 +- service/package.json | 12 +- service/src/setup-rest-api.ts | 4 +- yarn.lock | 152 +++++----- 13 files changed, 605 insertions(+), 128 deletions(-) create mode 100644 common/schemas/jwt-api.json create mode 100644 common/src/jwt-api.ts diff --git a/common/package.json b/common/package.json index 06936408..856554f1 100644 --- a/common/package.json +++ b/common/package.json @@ -30,6 +30,6 @@ "vitest": "^4.0.18" }, "dependencies": { - "@furystack/rest": "^8.0.39" + "@furystack/rest": "^8.0.40" } } diff --git a/common/schemas/boilerplate-api.json b/common/schemas/boilerplate-api.json index c437d34a..461b408b 100644 --- a/common/schemas/boilerplate-api.json +++ b/common/schemas/boilerplate-api.json @@ -128,6 +128,198 @@ ], "additionalProperties": false }, + "AuthorizedApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/currentUser": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/User" + } + }, + "required": [ + "result" + ], + "additionalProperties": false + }, + "/testAuthorized": { + "$ref": "#/definitions/TestAuthorizedEndpoint" + } + }, + "required": [ + "/currentUser", + "/testAuthorized" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PATCH": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET" + ], + "additionalProperties": false + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Name of the user" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of roles" + } + }, + "required": [ + "username", + "roles" + ], + "additionalProperties": false, + "description": "Class model that represents an application user" + }, "BoilerplateApi": { "type": "object", "properties": { @@ -470,28 +662,6 @@ "POST" ], "additionalProperties": false - }, - "User": { - "type": "object", - "properties": { - "username": { - "type": "string", - "description": "Name of the user" - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of roles" - } - }, - "required": [ - "username", - "roles" - ], - "additionalProperties": false, - "description": "Class model that represents an application user" } } } \ No newline at end of file diff --git a/common/schemas/jwt-api.json b/common/schemas/jwt-api.json new file mode 100644 index 00000000..811e0918 --- /dev/null +++ b/common/schemas/jwt-api.json @@ -0,0 +1,265 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/JwtApi", + "definitions": { + "JwtApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "POST": { + "type": "object", + "properties": { + "/jwt/login": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "refreshToken" + ], + "additionalProperties": false + } + }, + "required": [ + "body", + "result" + ], + "additionalProperties": false + }, + "/jwt/refresh": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + }, + "required": [ + "refreshToken" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + } + }, + "required": [ + "accessToken", + "refreshToken" + ], + "additionalProperties": false + } + }, + "required": [ + "body", + "result" + ], + "additionalProperties": false + }, + "/jwt/logout": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "refreshToken": { + "type": "string" + } + }, + "required": [ + "refreshToken" + ], + "additionalProperties": false + }, + "result": {} + }, + "required": [ + "body", + "result" + ], + "additionalProperties": false + } + }, + "required": [ + "/jwt/login", + "/jwt/refresh", + "/jwt/logout" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "POST" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/common/src/bin/create-schemas.ts b/common/src/bin/create-schemas.ts index 19556ad7..1f971e8e 100644 --- a/common/src/bin/create-schemas.ts +++ b/common/src/bin/create-schemas.ts @@ -25,6 +25,11 @@ export const apiValues: SchemaGenerationSetting[] = [ outputFile: './schemas/boilerplate-api.json', type: '*', }, + { + inputFile: './src/jwt-api.ts', + outputFile: './schemas/jwt-api.json', + type: '*', + }, ] export const exec = async (): Promise => { diff --git a/common/src/boilerplate-api.ts b/common/src/boilerplate-api.ts index 1a09c245..2431ff93 100644 --- a/common/src/boilerplate-api.ts +++ b/common/src/boilerplate-api.ts @@ -6,6 +6,13 @@ export type TestUrlParamsEndpoint = { url: { urlParam: string }; result: { urlPa export type TestPostBodyEndpoint = { body: { value: string }; result: { bodyValue: string } } export type TestAuthorizedEndpoint = { result: { message: string; timestamp: string } } +export interface AuthorizedApi extends RestApi { + GET: { + '/currentUser': { result: User } + '/testAuthorized': TestAuthorizedEndpoint + } +} + export interface BoilerplateApi extends RestApi { GET: { '/isAuthenticated': { result: { isAuthenticated: boolean } } diff --git a/common/src/index.ts b/common/src/index.ts index 04ca3ab8..e47c4b8f 100644 --- a/common/src/index.ts +++ b/common/src/index.ts @@ -1,2 +1,3 @@ export * from './boilerplate-api.js' +export * from './jwt-api.js' export * from './models/index.js' diff --git a/common/src/jwt-api.ts b/common/src/jwt-api.ts new file mode 100644 index 00000000..543e9371 --- /dev/null +++ b/common/src/jwt-api.ts @@ -0,0 +1,12 @@ +import type { RestApi } from '@furystack/rest' + +export interface JwtApi extends RestApi { + POST: { + '/jwt/login': { + body: { username: string; password: string } + result: { accessToken: string; refreshToken: string } + } + '/jwt/refresh': { body: { refreshToken: string }; result: { accessToken: string; refreshToken: string } } + '/jwt/logout': { body: { refreshToken: string }; result: unknown } + } +} diff --git a/frontend/package.json b/frontend/package.json index 5502ec29..f7036687 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,13 +17,13 @@ "vitest": "^4.0.18" }, "dependencies": { - "@furystack/auth-jwt": "^1.0.0", - "@furystack/core": "^15.2.1", + "@furystack/auth-jwt": "^2.0.0", + "@furystack/core": "^15.2.2", "@furystack/inject": "^12.0.30", "@furystack/logging": "^8.0.30", - "@furystack/rest-client-fetch": "^8.0.39", - "@furystack/shades": "^12.2.3", - "@furystack/shades-common-components": "^13.0.0", + "@furystack/rest-client-fetch": "^8.0.40", + "@furystack/shades": "^12.2.4", + "@furystack/shades-common-components": "^13.0.1", "@furystack/utils": "^8.1.10", "@types/node": "^25.3.1", "common": "workspace:^" diff --git a/frontend/src/services/boilerplate-api-client.ts b/frontend/src/services/boilerplate-api-client.ts index 899d7ac2..103b875b 100644 --- a/frontend/src/services/boilerplate-api-client.ts +++ b/frontend/src/services/boilerplate-api-client.ts @@ -1,27 +1,44 @@ -import { createJwtClient } from '@furystack/auth-jwt/client' +import { createJwtClient, createJwtTokenStore } from '@furystack/auth-jwt/client' import { Injectable } from '@furystack/inject' import { createClient } from '@furystack/rest-client-fetch' -import type { BoilerplateApi } from 'common' +import type { AuthorizedApi, JwtApi } from 'common' import { environmentOptions } from '../environment-options.js' -type JwtClient = ReturnType> -type ApiCall = ReturnType> +type AuthorizedApiCall = ReturnType> -const jwtClient: JwtClient = createJwtClient( - { endpointUrl: environmentOptions.serviceUrl, refreshThresholdSeconds: 10 }, - '/jwt/login', - '/jwt/refresh', - '/jwt/logout', -) +const jwtApiClient = createClient({ + endpointUrl: environmentOptions.serviceUrl, + requestInit: { credentials: 'include' }, +}) + +const tokenStore = createJwtTokenStore({ + refreshThresholdSeconds: 10, + login: async (credentials) => { + const { result } = await jwtApiClient({ method: 'POST', action: '/jwt/login', body: credentials }) + return result + }, + refresh: async (refreshToken) => { + const { result } = await jwtApiClient({ method: 'POST', action: '/jwt/refresh', body: { refreshToken } }) + return result + }, + logout: async (refreshToken) => { + await jwtApiClient({ method: 'POST', action: '/jwt/logout', body: { refreshToken } }) + }, +}) + +const authorizedClient = createJwtClient({ + endpointUrl: environmentOptions.serviceUrl, + tokenStore, +}) @Injectable({ lifetime: 'singleton' }) export class BoilerplateApiClient { - public call: ApiCall = jwtClient.call as ApiCall - public login = jwtClient.login - public logout = jwtClient.logout - public setTokens = jwtClient.setTokens + public call: AuthorizedApiCall = authorizedClient.call as AuthorizedApiCall + public login = tokenStore.login + public logout = tokenStore.logout + public setTokens = tokenStore.setTokens public get isAuthenticated(): boolean { - return jwtClient.isAuthenticated + return tokenStore.isAuthenticated } } diff --git a/package.json b/package.json index 1583b853..629278fd 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "type": "module", "devDependencies": { "@eslint/js": "^10.0.1", - "@furystack/yarn-plugin-changelog": "^1.0.4", + "@furystack/yarn-plugin-changelog": "^1.0.5", "@playwright/test": "^1.58.2", "@types/node": "^25.3.1", "@vitest/coverage-v8": "^4.0.18", diff --git a/service/package.json b/service/package.json index 0877ac9d..52faaf82 100644 --- a/service/package.json +++ b/service/package.json @@ -17,14 +17,14 @@ "vitest": "^4.0.18" }, "dependencies": { - "@furystack/auth-jwt": "^1.0.0", - "@furystack/core": "^15.2.1", - "@furystack/filesystem-store": "^7.0.39", + "@furystack/auth-jwt": "^2.0.0", + "@furystack/core": "^15.2.2", + "@furystack/filesystem-store": "^7.0.40", "@furystack/inject": "^12.0.30", "@furystack/logging": "^8.0.30", - "@furystack/repository": "^10.1.2", - "@furystack/rest-service": "^12.0.0", - "@furystack/security": "^7.0.0", + "@furystack/repository": "^10.1.3", + "@furystack/rest-service": "^12.1.0", + "@furystack/security": "^7.0.1", "common": "workspace:^" } } diff --git a/service/src/setup-rest-api.ts b/service/src/setup-rest-api.ts index 2010b6fa..9a4e7310 100644 --- a/service/src/setup-rest-api.ts +++ b/service/src/setup-rest-api.ts @@ -1,4 +1,4 @@ -import { JwtLoginAction, JwtLogoutAction, JwtRefreshAction } from '@furystack/auth-jwt' +import { createJwtLoginAction, JwtLogoutAction, JwtRefreshAction } from '@furystack/auth-jwt' import type { Injector } from '@furystack/inject' import { Authenticate, @@ -44,7 +44,7 @@ export const setupRestApi = async (injector: Injector): Promise => { POST: { '/login': LoginAction, '/logout': LogoutAction, - '/jwt/login': JwtLoginAction, + '/jwt/login': createJwtLoginAction(injector), '/jwt/refresh': JwtRefreshAction, '/jwt/logout': JwtLogoutAction, '/testPostBody': Validate({ schema: BoilerplateApiSchemas, schemaName: 'TestPostBodyEndpoint' })( diff --git a/yarn.lock b/yarn.lock index e967f328..dfdff8e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -569,19 +569,19 @@ __metadata: languageName: node linkType: hard -"@furystack/auth-jwt@npm:^1.0.0": - version: 1.0.0 - resolution: "@furystack/auth-jwt@npm:1.0.0" +"@furystack/auth-jwt@npm:^2.0.0": + version: 2.0.0 + resolution: "@furystack/auth-jwt@npm:2.0.0" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" - "@furystack/repository": "npm:^10.1.2" - "@furystack/rest": "npm:^8.0.39" - "@furystack/rest-client-fetch": "npm:^8.0.39" - "@furystack/rest-service": "npm:^12.0.0" - "@furystack/security": "npm:^7.0.0" + "@furystack/repository": "npm:^10.1.3" + "@furystack/rest": "npm:^8.0.40" + "@furystack/rest-client-fetch": "npm:^8.0.40" + "@furystack/rest-service": "npm:^12.1.0" + "@furystack/security": "npm:^7.0.1" "@furystack/utils": "npm:^8.1.10" - checksum: 10c0/08a98808f024e04129ed1650dea9ec7e1d311d20a63ae8fe3fd7e886a2173ea85f1f852c786949e53959fad06d914ef04ce0c720b1c6f417afeb854124e73d3a + checksum: 10c0/3f1b145093bbe9c73a75e1644553b07715e697f6d4749f1e804e15983f286f580cd48ea5857434dde5be0f3947e045ab7c4f89f5328f4b091c54cbf5780f3093 languageName: node linkType: hard @@ -595,24 +595,24 @@ __metadata: languageName: node linkType: hard -"@furystack/core@npm:^15.2.1": - version: 15.2.1 - resolution: "@furystack/core@npm:15.2.1" +"@furystack/core@npm:^15.2.2": + version: 15.2.2 + resolution: "@furystack/core@npm:15.2.2" dependencies: "@furystack/inject": "npm:^12.0.30" "@furystack/utils": "npm:^8.1.10" - checksum: 10c0/9a4009e4be30b027bab4a40f5b906132fe67ad914b36e4c4942bc49bac6832a376e67d81096cd5348f92c3839bdc8dc5a1870f591414685acea85d005fe1e107 + checksum: 10c0/3ae923b6ec5acfd88443b9b90ef1f796cce430ec8ebbe29a550d3afd1684321e293f92386405ea6ed180d5754f2a33bdd0a91e016133d529e01a9c0f9e90e460 languageName: node linkType: hard -"@furystack/filesystem-store@npm:^7.0.39": - version: 7.0.39 - resolution: "@furystack/filesystem-store@npm:7.0.39" +"@furystack/filesystem-store@npm:^7.0.40": + version: 7.0.40 + resolution: "@furystack/filesystem-store@npm:7.0.40" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" "@furystack/utils": "npm:^8.1.10" - checksum: 10c0/1ffdd2c24ebe44a3929581104e807c230921178cb83e9dae8a0716d167e633a47b9e8104cd07bdd62d46fce30b5ba1252c739f30581fe954b72401ff35e24bbd + checksum: 10c0/6e2f8c14b1885758d4e06d8778a9d422cfcd03d13c42864c3653b2bb979bd13b0e7d67c0569e0638aecabbc991348d494dc2cb49a7dddf9222aadfbbec8508a1 languageName: node linkType: hard @@ -634,88 +634,88 @@ __metadata: languageName: node linkType: hard -"@furystack/repository@npm:^10.1.2": - version: 10.1.2 - resolution: "@furystack/repository@npm:10.1.2" +"@furystack/repository@npm:^10.1.3": + version: 10.1.3 + resolution: "@furystack/repository@npm:10.1.3" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" "@furystack/utils": "npm:^8.1.10" - checksum: 10c0/c1542b0b4a1f000392bd659c53d1fe6d8478fa6cdffc95e68a7ca5179900d9156e012241104504721899d05f4d9083538ee3ca9d709ba12d9f0d736492e660e1 + checksum: 10c0/795a9cca29d7e7af6f0f4f91d3f3ae15e5449e99cf0eab4f3bc8496c93ed7bad37c0ede6c757eef3a6f86a6624b507aa4432e49906a09a7be1d726468a943a05 languageName: node linkType: hard -"@furystack/rest-client-fetch@npm:^8.0.39": - version: 8.0.39 - resolution: "@furystack/rest-client-fetch@npm:8.0.39" +"@furystack/rest-client-fetch@npm:^8.0.40": + version: 8.0.40 + resolution: "@furystack/rest-client-fetch@npm:8.0.40" dependencies: - "@furystack/rest": "npm:^8.0.39" + "@furystack/rest": "npm:^8.0.40" path-to-regexp: "npm:^8.3.0" - checksum: 10c0/c238dfd13ef43e528c8792fd062357527ad152d3015256cd64b9219eaaaa91e63d8fe9bdcd7f0c6c06f777e79af403bc211a85eaf95841c28927f10a17d69d22 + checksum: 10c0/7b5d251b0ff9f5aa0de38da7e160a1e06b48f315b4de98b8ee60929927dc2bc3e251db0d159050e3550105a385620debc4bd2e7263ac854d7e01dc47cc15b15f languageName: node linkType: hard -"@furystack/rest-service@npm:^12.0.0": - version: 12.0.0 - resolution: "@furystack/rest-service@npm:12.0.0" +"@furystack/rest-service@npm:^12.1.0": + version: 12.1.0 + resolution: "@furystack/rest-service@npm:12.1.0" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" - "@furystack/repository": "npm:^10.1.2" - "@furystack/rest": "npm:^8.0.39" - "@furystack/security": "npm:^7.0.0" + "@furystack/repository": "npm:^10.1.3" + "@furystack/rest": "npm:^8.0.40" + "@furystack/security": "npm:^7.0.1" "@furystack/utils": "npm:^8.1.10" ajv: "npm:^8.18.0" ajv-formats: "npm:^3.0.1" path-to-regexp: "npm:^8.3.0" - checksum: 10c0/3c76d369dd7153488e25b29abd169b5a1a76e0e8c01c00c0a5f7a6e47b626063d42e6cebfe323e4a6acf27ca8575ad2c87e31b222b2e13edc70cfaec00fb106f + checksum: 10c0/38ca5fbcc6af1fd7a617aa8fe3b1379fa37145ad36450c6936627ec9e846d91f0c090e15a0b3cf38ce11faab591d7df558696ff6f17473b84c09f65784acf0cc languageName: node linkType: hard -"@furystack/rest@npm:^8.0.39": - version: 8.0.39 - resolution: "@furystack/rest@npm:8.0.39" +"@furystack/rest@npm:^8.0.40": + version: 8.0.40 + resolution: "@furystack/rest@npm:8.0.40" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" - checksum: 10c0/fb2878d8bd4a44b15496d9598dc9fc9cfdaa54b86bdb9b5454988dcb9a27983433d7f4e9e7c8eee9cef10f79d9d82cbbdaa78bc4851776b1c9af155fac0e426a + checksum: 10c0/02df54f7dbf9a16d4eefbb0def19a218838d9e24f14ac106557c1371fd278ebb2ed202da1867408f3f62f94669e5b4c1eed69c910a1c628f555dc466246ea4dd languageName: node linkType: hard -"@furystack/security@npm:^7.0.0": - version: 7.0.0 - resolution: "@furystack/security@npm:7.0.0" +"@furystack/security@npm:^7.0.1": + version: 7.0.1 + resolution: "@furystack/security@npm:7.0.1" dependencies: - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" - "@furystack/repository": "npm:^10.1.2" - checksum: 10c0/8164354399b26236497b0d584896f758ed0fdd5b21689175e788ad65bc89c3bb517f775b6d327fa9a83d6988a1f8bdb128d981636b1414f4dba413a9604cf0e2 + "@furystack/repository": "npm:^10.1.3" + checksum: 10c0/eaeaf630c0be309edced336a478ec91e9b2617efbc15594af7750d35c7b57bf552869476d2b7d908b82814e06ab31f8b778ae4a608a1d8e6874c07950f6b24b4 languageName: node linkType: hard -"@furystack/shades-common-components@npm:^13.0.0": - version: 13.0.0 - resolution: "@furystack/shades-common-components@npm:13.0.0" +"@furystack/shades-common-components@npm:^13.0.1": + version: 13.0.1 + resolution: "@furystack/shades-common-components@npm:13.0.1" dependencies: "@furystack/cache": "npm:^6.0.0" - "@furystack/core": "npm:^15.2.1" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" - "@furystack/shades": "npm:^12.2.3" + "@furystack/shades": "npm:^12.2.4" "@furystack/utils": "npm:^8.1.10" path-to-regexp: "npm:^8.3.0" - checksum: 10c0/95908c5c2dd5469da2620c74192656db789fefba9027019d6c509593eaaf1c520ba496dbf932c7c6f94144e3d11b91a3cc37569ee04912257869f29abb3468fa + checksum: 10c0/82591863555353daaec2a3a151393a65e9d377b1ddb2b10cddb06828d8eb5e0b7948c7ec3f7d191ae931a264a10edd20b53c785b84b6e51055e50669ed79f842 languageName: node linkType: hard -"@furystack/shades@npm:^12.2.3": - version: 12.2.3 - resolution: "@furystack/shades@npm:12.2.3" +"@furystack/shades@npm:^12.2.4": + version: 12.2.4 + resolution: "@furystack/shades@npm:12.2.4" dependencies: "@furystack/inject": "npm:^12.0.30" - "@furystack/rest": "npm:^8.0.39" + "@furystack/rest": "npm:^8.0.40" "@furystack/utils": "npm:^8.1.10" path-to-regexp: "npm:^8.3.0" - checksum: 10c0/b23064f450f9a4b3003d3700f5efae248d29171db3d6d51f9fb45256ae4a01a418ba24ff94d7a0a460b5d79cb837f567dc478c4dc56078241d7585ce0ba71490 + checksum: 10c0/a3befc09434865741cb9790bfd5151100882496fcd2ce1ba6616e39aa3630f518f4af8399287f4e5f2f14653ad304d530aea288df18683607b25c878fcd549b2 languageName: node linkType: hard @@ -726,15 +726,15 @@ __metadata: languageName: node linkType: hard -"@furystack/yarn-plugin-changelog@npm:^1.0.4": - version: 1.0.4 - resolution: "@furystack/yarn-plugin-changelog@npm:1.0.4" +"@furystack/yarn-plugin-changelog@npm:^1.0.5": + version: 1.0.5 + resolution: "@furystack/yarn-plugin-changelog@npm:1.0.5" dependencies: "@yarnpkg/cli": "npm:^4.12.0" "@yarnpkg/core": "npm:^4.5.0" "@yarnpkg/fslib": "npm:^3.1.4" clipanion: "npm:^4.0.0-rc.4" - checksum: 10c0/1d414bdcd9ed3e7101c797c6130e526a577262e34b83085bd572ef8245695a52f02cdbf10b42914806c4228d2f46ee9f1be9eeb9b59bb8e7a5fca80a54b6459f + checksum: 10c0/311db67359791df7eebe16d9c954163c9999c09519c1da7798d5da980f600ea5443a1cef822eeadca28a0baea02b247ea9721c45d80aa615772f6ca96763b778 languageName: node linkType: hard @@ -2977,7 +2977,7 @@ __metadata: version: 0.0.0-use.local resolution: "common@workspace:common" dependencies: - "@furystack/rest": "npm:^8.0.39" + "@furystack/rest": "npm:^8.0.40" "@types/node": "npm:^25.3.1" ts-json-schema-generator: "npm:^2.5.0" vitest: "npm:^4.0.18" @@ -3893,13 +3893,13 @@ __metadata: resolution: "frontend@workspace:frontend" dependencies: "@codecov/vite-plugin": "npm:^1.9.1" - "@furystack/auth-jwt": "npm:^1.0.0" - "@furystack/core": "npm:^15.2.1" + "@furystack/auth-jwt": "npm:^2.0.0" + "@furystack/core": "npm:^15.2.2" "@furystack/inject": "npm:^12.0.30" "@furystack/logging": "npm:^8.0.30" - "@furystack/rest-client-fetch": "npm:^8.0.39" - "@furystack/shades": "npm:^12.2.3" - "@furystack/shades-common-components": "npm:^13.0.0" + "@furystack/rest-client-fetch": "npm:^8.0.40" + "@furystack/shades": "npm:^12.2.4" + "@furystack/shades-common-components": "npm:^13.0.1" "@furystack/utils": "npm:^8.1.10" "@types/node": "npm:^25.3.1" common: "workspace:^" @@ -4005,7 +4005,7 @@ __metadata: resolution: "furystack-boilerplate-app@workspace:." dependencies: "@eslint/js": "npm:^10.0.1" - "@furystack/yarn-plugin-changelog": "npm:^1.0.4" + "@furystack/yarn-plugin-changelog": "npm:^1.0.5" "@playwright/test": "npm:^1.58.2" "@types/node": "npm:^25.3.1" "@vitest/coverage-v8": "npm:^4.0.18" @@ -6239,14 +6239,14 @@ __metadata: version: 0.0.0-use.local resolution: "service@workspace:service" dependencies: - "@furystack/auth-jwt": "npm:^1.0.0" - "@furystack/core": "npm:^15.2.1" - "@furystack/filesystem-store": "npm:^7.0.39" + "@furystack/auth-jwt": "npm:^2.0.0" + "@furystack/core": "npm:^15.2.2" + "@furystack/filesystem-store": "npm:^7.0.40" "@furystack/inject": "npm:^12.0.30" "@furystack/logging": "npm:^8.0.30" - "@furystack/repository": "npm:^10.1.2" - "@furystack/rest-service": "npm:^12.0.0" - "@furystack/security": "npm:^7.0.0" + "@furystack/repository": "npm:^10.1.3" + "@furystack/rest-service": "npm:^12.1.0" + "@furystack/security": "npm:^7.0.1" "@types/node": "npm:^25.3.1" common: "workspace:^" typescript: "npm:^5.9.3" From 593f846835371bd86880ee3d285d935456e93646 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Thu, 26 Feb 2026 18:38:48 +0100 Subject: [PATCH 3/5] format fixes, versioning, changelogs --- .yarn/changelogs/common.87572845.md | 35 +++ .yarn/changelogs/frontend.87572845.md | 49 ++++ .../furystack-boilerplate-app.87572845.md | 34 +++ .yarn/changelogs/service.87572845.md | 42 ++++ .yarn/versions/87572845.yml | 5 + common/schemas/boilerplate-api.json | 214 ++++-------------- common/schemas/entities.json | 7 +- common/schemas/jwt-api.json | 82 ++----- 8 files changed, 234 insertions(+), 234 deletions(-) create mode 100644 .yarn/changelogs/common.87572845.md create mode 100644 .yarn/changelogs/frontend.87572845.md create mode 100644 .yarn/changelogs/furystack-boilerplate-app.87572845.md create mode 100644 .yarn/changelogs/service.87572845.md create mode 100644 .yarn/versions/87572845.yml diff --git a/.yarn/changelogs/common.87572845.md b/.yarn/changelogs/common.87572845.md new file mode 100644 index 00000000..10128820 --- /dev/null +++ b/.yarn/changelogs/common.87572845.md @@ -0,0 +1,35 @@ + +# common + + + +## ✨ Features + +- Added `JwtApi` type definition for JWT authentication endpoints (`/jwt/login`, `/jwt/refresh`, `/jwt/logout`) +- Added `AuthorizedApi` type for endpoints requiring JWT bearer authentication (`/currentUser`, `/testAuthorized`) +- Added JWT endpoints and `/testAuthorized` to `BoilerplateApi` +- Added `jwt-api.json` schema generation for the new JWT API types + +## ♻️ Refactoring + +- Changed `User` model to re-export from `@furystack/core` instead of defining a local class + +## ⬆️ Dependencies + +- Updated `@furystack/rest` from ^8.0.32 to ^8.0.40 +- Updated `@types/node` from ^25.0.10 to ^25.3.1 +- Updated `ts-json-schema-generator` from ^2.4.0 to ^2.5.0 diff --git a/.yarn/changelogs/frontend.87572845.md b/.yarn/changelogs/frontend.87572845.md new file mode 100644 index 00000000..b47d85c5 --- /dev/null +++ b/.yarn/changelogs/frontend.87572845.md @@ -0,0 +1,49 @@ + +# frontend + + + +## ✨ Features + +- Integrated JWT client-side authentication via `@furystack/auth-jwt/client` with automatic token refresh +- Added `Sidebar` component with a vertical navigation menu for routing between pages +- Added authorized endpoint test button on the HelloWorld page to exercise JWT token refresh +- Added `PageLayout` with a collapsible left drawer for responsive navigation +- Replaced `Router`/`RouteLink` with `NestedRouter`/`NestedRouteLink` for nested routing support + +## ♻️ Refactoring + +- Redesigned Login page using `Card`, `CardContent`, and `Alert` components for a polished card-based layout +- Redesigned HelloWorld page using `PageContainer`, `PageHeader`, and `Typography` for consistent structure +- Redesigned ButtonsDemo page using `PageContainer` and `PageHeader` with an action button in the header +- Redesigned Init and Offline pages using `Typography`, `Alert`, and `cssVariableTheme` for consistent theming +- Simplified `Header` by removing props-based configuration in favor of hardcoded branding with `DrawerToggleButton` +- Removed `Body` component; session-based routing moved into `Layout` +- Updated `SessionService` to use JWT-based login/logout via `BoilerplateApiClient` token store +- Updated `GithubLogo` to use the standalone `getTextColor` function + +## ⬆️ Dependencies + +- Added `@furystack/auth-jwt` ^2.0.0 +- Updated `@furystack/shades` from ^11.0.33 to ^12.2.4 +- Updated `@furystack/shades-common-components` from ^10.0.33 to ^13.0.1 +- Updated `@furystack/core` from ^15.0.32 to ^15.2.2 +- Updated `@furystack/rest-client-fetch` from ^8.0.32 to ^8.0.40 +- Updated `@furystack/inject` from ^12.0.26 to ^12.0.30 +- Updated `@furystack/logging` from ^8.0.26 to ^8.0.30 +- Updated `@furystack/utils` from ^8.1.8 to ^8.1.10 +- Updated `@types/node` from ^25.0.10 to ^25.3.1 diff --git a/.yarn/changelogs/furystack-boilerplate-app.87572845.md b/.yarn/changelogs/furystack-boilerplate-app.87572845.md new file mode 100644 index 00000000..a1cce567 --- /dev/null +++ b/.yarn/changelogs/furystack-boilerplate-app.87572845.md @@ -0,0 +1,34 @@ + +# furystack-boilerplate-app + + + +## ⬆️ Dependencies + +- Updated `eslint` from ^9.39.2 to ^10.0.2 +- Updated `@eslint/js` from ^9.39.2 to ^10.0.1 +- Updated `typescript-eslint` from ^8.53.1 to ^8.56.1 +- Updated `@playwright/test` from ^1.58.0 to ^1.58.2 +- Updated `@furystack/yarn-plugin-changelog` from ^1.0.1 to ^1.0.5 +- Updated `eslint-plugin-jsdoc` from ^62.4.0 to ^62.7.1 +- Updated `eslint-plugin-playwright` from ^2.5.0 to ^2.7.1 +- Updated `rimraf` from ^6.1.2 to ^6.1.3 +- Updated `@types/node` from ^25.0.10 to ^25.3.1 + +## 🔧 Chores + +- Renamed `test:unit` script to `test` diff --git a/.yarn/changelogs/service.87572845.md b/.yarn/changelogs/service.87572845.md new file mode 100644 index 00000000..7bb0d8de --- /dev/null +++ b/.yarn/changelogs/service.87572845.md @@ -0,0 +1,42 @@ + +# service + + + +## ✨ Features + +- Added JWT authentication support via `@furystack/auth-jwt` with configurable secret and token expiration +- Added `/jwt/login`, `/jwt/refresh`, and `/jwt/logout` REST endpoints for JWT-based auth flow +- Added `/testAuthorized` endpoint protected by the `Authenticate()` middleware +- Added `RefreshToken` and `PasswordResetToken` stores and DataSets + +## ♻️ Refactoring + +- Split monolithic `config.ts` and `service.ts` into focused modules: `root-injector.ts`, `setup-store.ts`, `setup-rest-api.ts`, `get-cors-options.ts`, `get-port.ts`, and `authorization/authorized-only.ts` +- Seed now uses DataSets (via `getDataSetFor`) with a system identity context instead of raw PhysicalStore access + +## ⬆️ Dependencies + +- Added `@furystack/auth-jwt` ^2.0.0 +- Updated `@furystack/core` from ^15.0.32 to ^15.2.2 +- Updated `@furystack/rest-service` from ^10.1.3 to ^12.1.0 +- Updated `@furystack/security` from ^6.0.32 to ^7.0.1 +- Updated `@furystack/repository` from ^10.0.32 to ^10.1.3 +- Updated `@furystack/inject` from ^12.0.26 to ^12.0.30 +- Updated `@furystack/logging` from ^8.0.26 to ^8.0.30 +- Updated `@furystack/filesystem-store` from ^7.0.32 to ^7.0.40 +- Updated `@types/node` from ^25.0.10 to ^25.3.1 diff --git a/.yarn/versions/87572845.yml b/.yarn/versions/87572845.yml new file mode 100644 index 00000000..6e6773ba --- /dev/null +++ b/.yarn/versions/87572845.yml @@ -0,0 +1,5 @@ +releases: + common: patch + frontend: patch + furystack-boilerplate-app: patch + service: patch diff --git a/common/schemas/boilerplate-api.json b/common/schemas/boilerplate-api.json index 461b408b..a2b783fe 100644 --- a/common/schemas/boilerplate-api.json +++ b/common/schemas/boilerplate-api.json @@ -11,9 +11,7 @@ "type": "string" } }, - "required": [ - "param1" - ], + "required": ["param1"], "additionalProperties": false }, "result": { @@ -23,16 +21,11 @@ "type": "string" } }, - "required": [ - "param1Value" - ], + "required": ["param1Value"], "additionalProperties": false } }, - "required": [ - "query", - "result" - ], + "required": ["query", "result"], "additionalProperties": false }, "TestUrlParamsEndpoint": { @@ -45,9 +38,7 @@ "type": "string" } }, - "required": [ - "urlParam" - ], + "required": ["urlParam"], "additionalProperties": false }, "result": { @@ -57,16 +48,11 @@ "type": "string" } }, - "required": [ - "urlParamValue" - ], + "required": ["urlParamValue"], "additionalProperties": false } }, - "required": [ - "url", - "result" - ], + "required": ["url", "result"], "additionalProperties": false }, "TestPostBodyEndpoint": { @@ -79,9 +65,7 @@ "type": "string" } }, - "required": [ - "value" - ], + "required": ["value"], "additionalProperties": false }, "result": { @@ -91,16 +75,11 @@ "type": "string" } }, - "required": [ - "bodyValue" - ], + "required": ["bodyValue"], "additionalProperties": false } }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "TestAuthorizedEndpoint": { @@ -116,16 +95,11 @@ "type": "string" } }, - "required": [ - "message", - "timestamp" - ], + "required": ["message", "timestamp"], "additionalProperties": false } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "AuthorizedApi": { @@ -141,19 +115,14 @@ "$ref": "#/definitions/User" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "/testAuthorized": { "$ref": "#/definitions/TestAuthorizedEndpoint" } }, - "required": [ - "/currentUser", - "/testAuthorized" - ], + "required": ["/currentUser", "/testAuthorized"], "additionalProperties": false }, "POST": { @@ -167,9 +136,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -184,9 +151,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -201,9 +166,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -218,9 +181,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -235,9 +196,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -252,9 +211,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -269,9 +226,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -286,16 +241,12 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET" - ], + "required": ["GET"], "additionalProperties": false }, "User": { @@ -313,10 +264,7 @@ "description": "List of roles" } }, - "required": [ - "username", - "roles" - ], + "required": ["username", "roles"], "additionalProperties": false, "description": "Class model that represents an application user" }, @@ -336,15 +284,11 @@ "type": "boolean" } }, - "required": [ - "isAuthenticated" - ], + "required": ["isAuthenticated"], "additionalProperties": false } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "/currentUser": { @@ -354,9 +298,7 @@ "$ref": "#/definitions/User" } }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "/testQuery": { @@ -369,13 +311,7 @@ "$ref": "#/definitions/TestAuthorizedEndpoint" } }, - "required": [ - "/isAuthenticated", - "/currentUser", - "/testQuery", - "/testUrlParams/:urlParam", - "/testAuthorized" - ], + "required": ["/isAuthenticated", "/currentUser", "/testQuery", "/testUrlParams/:urlParam", "/testAuthorized"], "additionalProperties": false }, "POST": { @@ -397,17 +333,11 @@ "type": "string" } }, - "required": [ - "username", - "password" - ], + "required": ["username", "password"], "additionalProperties": false } }, - "required": [ - "result", - "body" - ], + "required": ["result", "body"], "additionalProperties": false }, "/logout": { @@ -415,9 +345,7 @@ "properties": { "result": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false }, "/jwt/login": { @@ -433,10 +361,7 @@ "type": "string" } }, - "required": [ - "username", - "password" - ], + "required": ["username", "password"], "additionalProperties": false }, "result": { @@ -449,17 +374,11 @@ "type": "string" } }, - "required": [ - "accessToken", - "refreshToken" - ], + "required": ["accessToken", "refreshToken"], "additionalProperties": false } }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "/jwt/refresh": { @@ -472,9 +391,7 @@ "type": "string" } }, - "required": [ - "refreshToken" - ], + "required": ["refreshToken"], "additionalProperties": false }, "result": { @@ -487,17 +404,11 @@ "type": "string" } }, - "required": [ - "accessToken", - "refreshToken" - ], + "required": ["accessToken", "refreshToken"], "additionalProperties": false } }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "/jwt/logout": { @@ -510,31 +421,19 @@ "type": "string" } }, - "required": [ - "refreshToken" - ], + "required": ["refreshToken"], "additionalProperties": false }, "result": {} }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "/testPostBody": { "$ref": "#/definitions/TestPostBodyEndpoint" } }, - "required": [ - "/login", - "/logout", - "/jwt/login", - "/jwt/refresh", - "/jwt/logout", - "/testPostBody" - ], + "required": ["/login", "/logout", "/jwt/login", "/jwt/refresh", "/jwt/logout", "/testPostBody"], "additionalProperties": false }, "PATCH": { @@ -548,9 +447,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -565,9 +462,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -582,9 +477,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -599,9 +492,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -616,9 +507,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -633,9 +522,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -650,18 +537,13 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "GET", - "POST" - ], + "required": ["GET", "POST"], "additionalProperties": false } } -} \ No newline at end of file +} diff --git a/common/schemas/entities.json b/common/schemas/entities.json index 91fb5925..aebcdb7a 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -17,12 +17,9 @@ "description": "List of roles" } }, - "required": [ - "username", - "roles" - ], + "required": ["username", "roles"], "additionalProperties": false, "description": "Class model that represents an application user" } } -} \ No newline at end of file +} diff --git a/common/schemas/jwt-api.json b/common/schemas/jwt-api.json index 811e0918..5afd7be6 100644 --- a/common/schemas/jwt-api.json +++ b/common/schemas/jwt-api.json @@ -16,9 +16,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -38,10 +36,7 @@ "type": "string" } }, - "required": [ - "username", - "password" - ], + "required": ["username", "password"], "additionalProperties": false }, "result": { @@ -54,17 +49,11 @@ "type": "string" } }, - "required": [ - "accessToken", - "refreshToken" - ], + "required": ["accessToken", "refreshToken"], "additionalProperties": false } }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "/jwt/refresh": { @@ -77,9 +66,7 @@ "type": "string" } }, - "required": [ - "refreshToken" - ], + "required": ["refreshToken"], "additionalProperties": false }, "result": { @@ -92,17 +79,11 @@ "type": "string" } }, - "required": [ - "accessToken", - "refreshToken" - ], + "required": ["accessToken", "refreshToken"], "additionalProperties": false } }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false }, "/jwt/logout": { @@ -115,25 +96,16 @@ "type": "string" } }, - "required": [ - "refreshToken" - ], + "required": ["refreshToken"], "additionalProperties": false }, "result": {} }, - "required": [ - "body", - "result" - ], + "required": ["body", "result"], "additionalProperties": false } }, - "required": [ - "/jwt/login", - "/jwt/refresh", - "/jwt/logout" - ], + "required": ["/jwt/login", "/jwt/refresh", "/jwt/logout"], "additionalProperties": false }, "PATCH": { @@ -147,9 +119,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -164,9 +134,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -181,9 +149,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -198,9 +164,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -215,9 +179,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -232,9 +194,7 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } }, @@ -249,17 +209,13 @@ "body": {}, "headers": {} }, - "required": [ - "result" - ], + "required": ["result"], "additionalProperties": false } } }, - "required": [ - "POST" - ], + "required": ["POST"], "additionalProperties": false } } -} \ No newline at end of file +} From c650304bb52e21fe6c59c5574ebe95c52643d670 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Thu, 26 Feb 2026 18:44:01 +0100 Subject: [PATCH 4/5] dispose fix --- service/src/root-injector.ts | 2 -- service/src/service.ts | 2 ++ service/users.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/service/src/root-injector.ts b/service/src/root-injector.ts index 9a3918ad..f4021ae8 100644 --- a/service/src/root-injector.ts +++ b/service/src/root-injector.ts @@ -1,7 +1,5 @@ import { Injector } from '@furystack/inject' import { useLogging, VerboseConsoleLogger } from '@furystack/logging' -import { attachShutdownHandler } from './shutdown-handler.js' export const injector = new Injector() useLogging(injector, VerboseConsoleLogger) -void attachShutdownHandler(injector) diff --git a/service/src/service.ts b/service/src/service.ts index 7baf7f4b..552950c6 100644 --- a/service/src/service.ts +++ b/service/src/service.ts @@ -1,7 +1,9 @@ import { injector } from './root-injector.js' +import { attachShutdownHandler } from './shutdown-handler.js' import { setupStore } from './setup-store.js' import { setupRestApi } from './setup-rest-api.js' +void attachShutdownHandler(injector) setupStore(injector) setupRestApi(injector).catch((err) => { diff --git a/service/users.json b/service/users.json index 9b7e8573..2289975a 100644 --- a/service/users.json +++ b/service/users.json @@ -1 +1 @@ -[{ "username": "testuser", "roles": [] }] +[{"username":"testuser","roles":[]}] \ No newline at end of file From 5566c64a258f48a9c7f97296803f183531d5c577 Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Thu, 26 Feb 2026 18:51:43 +0100 Subject: [PATCH 5/5] prettier fix --- service/users.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/users.json b/service/users.json index 2289975a..9b7e8573 100644 --- a/service/users.json +++ b/service/users.json @@ -1 +1 @@ -[{"username":"testuser","roles":[]}] \ No newline at end of file +[{ "username": "testuser", "roles": [] }]