diff --git a/README.md b/README.md index 6578413..9dae3bb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -# 3Sixty API +# 3Sixty API Auth -This repository provides a modular API stack for defining contracts, implementing services, exposing server endpoints, and consuming those endpoints from client applications. The packages are designed to work together so teams can compose reliable API capabilities without duplicating cross-cutting concerns such as routing, request processing, authentication, and tenant context handling. +This repository provides authentication for the 3Sixty API stack, covering sign-in, token refresh, password management and user administration backed by entity storage, with an auth header processor that resolves the caller for each request. -Alongside the core API building blocks, the repository also includes entity storage authentication and tenant processing components that can be adopted independently or integrated into a broader platform architecture. The overall goal is to keep API development consistent, maintainable, and straightforward to extend across different deployment contexts. +Alongside these flows, the repository includes authentication audit logging, rate limiting of sensitive actions and a tenant override processor that lets callers holding the escalated privilege role act on another tenant. Route access is governed by the authorization packages, so each route declares its default authorization rather than checking scopes itself. ## Packages +- [api-auth-models](packages/api-auth-models/README.md) - Contracts for authentication audit and rate limiting. +- [api-auth-service](packages/api-auth-service/README.md) - Authentication audit and rate limiting services, tenant override processing and audit REST routes backed by entity storage. +- [api-auth-rest-client](packages/api-auth-rest-client/README.md) - REST client for the authentication audit endpoints. - [api-auth-entity-storage-models](packages/api-auth-entity-storage-models/README.md) - Contracts for authentication flows and admin user management with entity storage. - [api-auth-entity-storage-service](packages/api-auth-entity-storage-service/README.md) - Authentication service implementation and REST routes backed by entity storage. - [api-auth-entity-storage-rest-client](packages/api-auth-entity-storage-rest-client/README.md) - REST clients for authentication and admin operations against entity storage endpoints. diff --git a/docs/packages.md b/docs/packages.md index 3972523..3a37f8e 100644 --- a/docs/packages.md +++ b/docs/packages.md @@ -1,52 +1,28 @@ -# API Packages +# API Auth Packages -## api-models +## api-auth-models -This package provides shared API contracts, route types, and response models used across services and clients. +This package provides contracts for authentication audit and rate limiting. -- [README](../packages/api-models/README.md) -- [Examples](../packages/api-models/docs/examples.md) -- [Changelog](../packages/api-models/docs/changelog.md) +- [README](../packages/api-auth-models/README.md) +- [Examples](../packages/api-auth-models/docs/examples.md) +- [Changelog](../packages/api-auth-models/docs/changelog.md) -## api-core +## api-auth-service -This package provides base client classes and common helpers for building HTTP and socket integrations. +This package provides authentication audit and rate limiting services, tenant override processing and audit REST routes backed by entity storage. -- [README](../packages/api-core/README.md) -- [Examples](../packages/api-core/docs/examples.md) -- [Changelog](../packages/api-core/docs/changelog.md) +- [README](../packages/api-auth-service/README.md) +- [Examples](../packages/api-auth-service/docs/examples.md) +- [Changelog](../packages/api-auth-service/docs/changelog.md) -## api-processors +## api-auth-rest-client -This package provides reusable request and route processors for logging, context handling, and content negotiation. +This package provides rEST client for the authentication audit endpoints. -- [README](../packages/api-processors/README.md) -- [Examples](../packages/api-processors/docs/examples.md) -- [Changelog](../packages/api-processors/docs/changelog.md) - -## api-server-fastify - -This package provides Fastify web server integration for exposing API routes with consistent runtime behaviour. It builds on the [Fastify](https://fastify.dev/) framework to offer a predictable hosting layer for API services. - -- [README](../packages/api-server-fastify/README.md) -- [Examples](../packages/api-server-fastify/docs/examples.md) -- [Changelog](../packages/api-server-fastify/docs/changelog.md) - -## api-service - -This package provides information and hosting service implementations with generated REST route handlers. - -- [README](../packages/api-service/README.md) -- [Examples](../packages/api-service/docs/examples.md) -- [Changelog](../packages/api-service/docs/changelog.md) - -## api-rest-client - -This package provides a REST client implementation for consuming information and hosting endpoints. - -- [README](../packages/api-rest-client/README.md) -- [Examples](../packages/api-rest-client/docs/examples.md) -- [Changelog](../packages/api-rest-client/docs/changelog.md) +- [README](../packages/api-auth-rest-client/README.md) +- [Examples](../packages/api-auth-rest-client/docs/examples.md) +- [Changelog](../packages/api-auth-rest-client/docs/changelog.md) ## api-auth-entity-storage-models @@ -58,7 +34,7 @@ This package provides contracts for authentication flows and admin user manageme ## api-auth-entity-storage-service -This package provides an authentication service implementation and REST routes backed by entity storage. +This package provides authentication service implementation and REST routes backed by entity storage. - [README](../packages/api-auth-entity-storage-service/README.md) - [Examples](../packages/api-auth-entity-storage-service/docs/examples.md) @@ -66,16 +42,8 @@ This package provides an authentication service implementation and REST routes b ## api-auth-entity-storage-rest-client -This package provides REST clients for authentication and admin operations against entity storage endpoints. +This package provides rEST clients for authentication and admin operations against entity storage endpoints. - [README](../packages/api-auth-entity-storage-rest-client/README.md) - [Examples](../packages/api-auth-entity-storage-rest-client/docs/examples.md) - [Changelog](../packages/api-auth-entity-storage-rest-client/docs/changelog.md) - -## api-tenant-processor - -This package provides tenant resolution services and route handlers that derive tenant context from API keys. - -- [README](../packages/api-tenant-processor/README.md) -- [Examples](../packages/api-tenant-processor/docs/examples.md) -- [Changelog](../packages/api-tenant-processor/docs/changelog.md) diff --git a/packages/api-auth-entity-storage-models/docs/examples.md b/packages/api-auth-entity-storage-models/docs/examples.md index 1d33972..bcbd2d2 100644 --- a/packages/api-auth-entity-storage-models/docs/examples.md +++ b/packages/api-auth-entity-storage-models/docs/examples.md @@ -47,8 +47,7 @@ const createRequest: IAdminUserCreateRequest = { email: 'bob@example.org', password: 'initial-password', userIdentity: 'did:example:bob', - organizationIdentity: 'did:example:org', - scope: ['admin', 'auditor'] + organizationIdentity: 'did:example:org' } }; @@ -57,18 +56,20 @@ const updateRequest: IAdminUserUpdateRequest = { email: 'bob@example.org' }, body: { - userIdentity: 'did:example:bob:ops', - scope: ['admin'] + userIdentity: 'did:example:bob:ops' } }; -console.log(createRequest.body.scope.length); // 2 +console.log(updateRequest.body.userIdentity); // did:example:bob:ops ``` ## Authentication Responses ```typescript -import type { ILoginResponse } from '@3sixty/api-auth-entity-storage-models'; +import type { + ILoginResponse, + IPasswordPolicyResponse +} from '@3sixty/api-auth-entity-storage-models'; const loginResponse: ILoginResponse = { body: { @@ -76,5 +77,13 @@ const loginResponse: ILoginResponse = { } }; +const passwordPolicyResponse: IPasswordPolicyResponse = { + body: { + minPasswordLength: 8, + maxPasswordLength: 128 + } +}; + console.log(loginResponse.body.expiry); // 3600 +console.log(passwordPolicyResponse.body.minPasswordLength); // 8 ``` diff --git a/packages/api-auth-entity-storage-models/docs/reference/index.md b/packages/api-auth-entity-storage-models/docs/reference/index.md index 6762402..a4cfe2f 100644 --- a/packages/api-auth-entity-storage-models/docs/reference/index.md +++ b/packages/api-auth-entity-storage-models/docs/reference/index.md @@ -3,11 +3,7 @@ ## Interfaces - [IAuthenticationAdminComponent](interfaces/IAuthenticationAdminComponent.md) -- [IAuthenticationAuditComponent](interfaces/IAuthenticationAuditComponent.md) -- [IAuthenticationAuditEntry](interfaces/IAuthenticationAuditEntry.md) - [IAuthenticationComponent](interfaces/IAuthenticationComponent.md) -- [IAuthenticationRateActionConfig](interfaces/IAuthenticationRateActionConfig.md) -- [IAuthenticationRateComponent](interfaces/IAuthenticationRateComponent.md) - [IAuthenticationUser](interfaces/IAuthenticationUser.md) - [IAuthenticationUserSecure](interfaces/IAuthenticationUserSecure.md) - [IAdminUserCreateRequest](interfaces/IAdminUserCreateRequest.md) @@ -17,24 +13,9 @@ - [IAdminUserRemoveRequest](interfaces/IAdminUserRemoveRequest.md) - [IAdminUserUpdatePasswordRequest](interfaces/IAdminUserUpdatePasswordRequest.md) - [IAdminUserUpdateRequest](interfaces/IAdminUserUpdateRequest.md) -- [IAuditCreateRequest](interfaces/IAuditCreateRequest.md) -- [IAuditGetRequest](interfaces/IAuditGetRequest.md) -- [IAuditGetResponse](interfaces/IAuditGetResponse.md) -- [IAuditQueryRequest](interfaces/IAuditQueryRequest.md) -- [IAuditQueryResponse](interfaces/IAuditQueryResponse.md) -- [IAuditRemoveRequest](interfaces/IAuditRemoveRequest.md) -- [IAuditUpdateRequest](interfaces/IAuditUpdateRequest.md) - [ILoginRequest](interfaces/ILoginRequest.md) - [ILoginResponse](interfaces/ILoginResponse.md) - [ILogoutRequest](interfaces/ILogoutRequest.md) - [IRefreshTokenRequest](interfaces/IRefreshTokenRequest.md) - [IRefreshTokenResponse](interfaces/IRefreshTokenResponse.md) - [IUpdatePasswordRequest](interfaces/IUpdatePasswordRequest.md) - -## Type Aliases - -- [AuthAuditEvent](type-aliases/AuthAuditEvent.md) - -## Variables - -- [AuthAuditEvent](variables/AuthAuditEvent.md) diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUser.md b/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUser.md index 1a9502d..19e3b4b 100644 --- a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUser.md +++ b/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUser.md @@ -29,11 +29,3 @@ The user identity. > **organizationIdentity**: `string` The users organization. - -*** - -### scope {#scope} - -> **scope**: `string`[] - -The scope assigned to the user, comma separated. diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUserSecure.md b/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUserSecure.md index 81adf21..2947c9e 100644 --- a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUserSecure.md +++ b/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationUserSecure.md @@ -44,18 +44,6 @@ The users organization. *** -### scope {#scope} - -> **scope**: `string`[] - -The scope assigned to the user, comma separated. - -#### Inherited from - -[`IAuthenticationUser`](IAuthenticationUser.md).[`scope`](IAuthenticationUser.md#scope) - -*** - ### password {#password} > **password**: `string` diff --git a/packages/api-auth-entity-storage-models/src/index.ts b/packages/api-auth-entity-storage-models/src/index.ts index ff0bf13..4e984d2 100644 --- a/packages/api-auth-entity-storage-models/src/index.ts +++ b/packages/api-auth-entity-storage-models/src/index.ts @@ -7,25 +7,15 @@ export * from "./models/api/IAdminUserGetResponse.js"; export * from "./models/api/IAdminUserRemoveRequest.js"; export * from "./models/api/IAdminUserUpdatePasswordRequest.js"; export * from "./models/api/IAdminUserUpdateRequest.js"; -export * from "./models/api/IAuditCreateRequest.js"; -export * from "./models/api/IAuditGetRequest.js"; -export * from "./models/api/IAuditGetResponse.js"; -export * from "./models/api/IAuditQueryRequest.js"; -export * from "./models/api/IAuditQueryResponse.js"; -export * from "./models/api/IAuditRemoveRequest.js"; -export * from "./models/api/IAuditUpdateRequest.js"; export * from "./models/api/ILoginRequest.js"; export * from "./models/api/ILoginResponse.js"; export * from "./models/api/ILogoutRequest.js"; +export * from "./models/api/IPasswordPolicyResponse.js"; export * from "./models/api/IRefreshTokenRequest.js"; export * from "./models/api/IRefreshTokenResponse.js"; export * from "./models/api/IUpdatePasswordRequest.js"; -export * from "./models/authAuditEvent.js"; export * from "./models/IAuthenticationAdminComponent.js"; -export * from "./models/IAuthenticationAuditComponent.js"; -export * from "./models/IAuthenticationAuditEntry.js"; export * from "./models/IAuthenticationComponent.js"; -export * from "./models/IAuthenticationRateActionConfig.js"; -export * from "./models/IAuthenticationRateComponent.js"; export * from "./models/IAuthenticationUser.js"; export * from "./models/IAuthenticationUserSecure.js"; +export * from "./models/IPasswordOptions.js"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts index ebc1044..7b1c372 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts @@ -1,6 +1,7 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; +import type { IPasswordOptions } from "./IPasswordOptions.js"; /** * Contract definition for authentication component. @@ -44,4 +45,10 @@ export interface IAuthenticationComponent extends IComponent { * @returns A promise that resolves when the password has been updated. */ updatePassword(currentPassword: string, newPassword: string): Promise; + + /** + * Get the password policy used for password validation. + * @returns The password policy options. + */ + passwordPolicy(): Promise; } diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts index 8880132..a8b8e2c 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts @@ -19,9 +19,4 @@ export interface IAuthenticationUser { * The users organization. */ organizationIdentity: string; - - /** - * The scope assigned to the user, comma separated. - */ - scope: string[]; } diff --git a/packages/api-auth-entity-storage-models/src/models/IPasswordOptions.ts b/packages/api-auth-entity-storage-models/src/models/IPasswordOptions.ts new file mode 100644 index 0000000..9a54b7b --- /dev/null +++ b/packages/api-auth-entity-storage-models/src/models/IPasswordOptions.ts @@ -0,0 +1,17 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +/** + * Password length validation options. + */ +export interface IPasswordOptions { + /** + * The minimum password length for validation. + */ + minPasswordLength?: number; + + /** + * The maximum password length for validation. + */ + maxPasswordLength?: number; +} diff --git a/packages/api-auth-entity-storage-models/src/models/api/IPasswordPolicyResponse.ts b/packages/api-auth-entity-storage-models/src/models/api/IPasswordPolicyResponse.ts new file mode 100644 index 0000000..d5ea47e --- /dev/null +++ b/packages/api-auth-entity-storage-models/src/models/api/IPasswordPolicyResponse.ts @@ -0,0 +1,13 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import type { IPasswordOptions } from "../IPasswordOptions.js"; + +/** + * Get the password policy. + */ +export interface IPasswordPolicyResponse { + /** + * The body of the response. + */ + body: IPasswordOptions; +} diff --git a/packages/api-auth-entity-storage-rest-client/docs/examples.md b/packages/api-auth-entity-storage-rest-client/docs/examples.md index 25d841f..228afe3 100644 --- a/packages/api-auth-entity-storage-rest-client/docs/examples.md +++ b/packages/api-auth-entity-storage-rest-client/docs/examples.md @@ -18,14 +18,12 @@ await adminClient.create({ email: 'ops@example.org', password: 'StrongPassword123', userIdentity: 'did:example:ops', - organizationIdentity: 'did:example:core', - scope: ['admin'] + organizationIdentity: 'did:example:core' }); await adminClient.update({ email: 'ops@example.org', - userIdentity: 'did:example:ops:team', - scope: ['admin', 'support'] + userIdentity: 'did:example:ops:team' }); await adminClient.updatePassword('ops@example.org', 'StrongPassword124', 'StrongPassword123'); @@ -42,7 +40,7 @@ const adminClient = new EntityStorageAuthenticationAdminRestClient({ }); const byEmail = await adminClient.get('ops@example.org'); -const byIdentity = await adminClient.getByIdentity('ops-team@example.org'); +const byIdentity = await adminClient.getByIdentity('did:example:ops:team'); await adminClient.remove('ops@example.org'); console.log(byEmail.email); // ops@example.org @@ -62,6 +60,9 @@ const authClient = new EntityStorageAuthenticationRestClient({ console.log(authClient.className()); // EntityStorageAuthenticationRestClient +const policy = await authClient.passwordPolicy(); +console.log(policy.minPasswordLength); // 8 + const loginResponse = await authClient.login('alice@example.org', 'correct-horse-battery-staple'); await authClient.updatePassword('correct-horse-battery-staple', 'correct-horse-battery-staple-2'); @@ -71,41 +72,3 @@ const refreshResponse = await authClient.refresh(loginResponse.token); await authClient.logout(refreshResponse.token); console.log(refreshResponse.expiry > 0); // true ``` - -## EntityStorageAuthenticationAuditRestClient - -```typescript -import { EntityStorageAuthenticationAuditRestClient } from '@3sixty/api-auth-entity-storage-rest-client'; - -const auditClient = new EntityStorageAuthenticationAuditRestClient({ - endpoint: 'https://api.example.org', - pathPrefix: 'v1' -}); - -console.log(auditClient.className()); // EntityStorageAuthenticationAuditRestClient - -const auditEntryId = await auditClient.create({ - event: 'login-success', - actorId: 'did:example:user:alice', - organizationId: 'did:example:org:core', - tenantId: 'did:example:tenant:alpha', - data: { - channel: 'web' - } -}); - -const auditResult = await auditClient.query( - { - actorId: 'did:example:user:alice', - event: 'login-success', - startDate: '2026-04-01T00:00:00.000Z', - endDate: '2026-04-30T23:59:59.999Z' - }, - undefined, - 25 -); - -console.log(auditEntryId); // /authentication/audit/018f2f67bb9d4a0caad8386f56df85ce -console.log(auditResult.entries.length); // 1 -console.log(auditResult.cursor); // eyJpZCI6IjAxOGYyZjY3YmI5ZDRhMGNhYWQ4Mzg2ZjU2ZGY4NWNlIn0= -``` diff --git a/packages/api-auth-entity-storage-rest-client/docs/reference/index.md b/packages/api-auth-entity-storage-rest-client/docs/reference/index.md index 80aa197..9a3b852 100644 --- a/packages/api-auth-entity-storage-rest-client/docs/reference/index.md +++ b/packages/api-auth-entity-storage-rest-client/docs/reference/index.md @@ -3,7 +3,6 @@ ## Classes - [EntityStorageAuthenticationAdminRestClient](classes/EntityStorageAuthenticationAdminRestClient.md) -- [EntityStorageAuthenticationAuditRestClient](classes/EntityStorageAuthenticationAuditRestClient.md) - [EntityStorageAuthenticationRestClient](classes/EntityStorageAuthenticationRestClient.md) ## Interfaces diff --git a/packages/api-auth-entity-storage-rest-client/docs/reference/interfaces/IEntityStorageAuthenticationRestClientConstructorOptions.md b/packages/api-auth-entity-storage-rest-client/docs/reference/interfaces/IEntityStorageAuthenticationRestClientConstructorOptions.md index d8fd4ab..2bc5e41 100644 --- a/packages/api-auth-entity-storage-rest-client/docs/reference/interfaces/IEntityStorageAuthenticationRestClientConstructorOptions.md +++ b/packages/api-auth-entity-storage-rest-client/docs/reference/interfaces/IEntityStorageAuthenticationRestClientConstructorOptions.md @@ -82,6 +82,18 @@ Include credentials in the request, defaults to true. *** +### processorTypes? {#processortypes} + +> `optional` **processorTypes?**: `string`[] + +The types of the processors to run around each request, resolved from the `RestClientProcessorFactory`. + +#### Inherited from + +`IBaseRestClientConfig.processorTypes` + +*** + ### customHeaders? {#customheaders} > `optional` **customHeaders?**: () => `Promise`\<`IHttpHeaders`\> diff --git a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts index 0a4c159..2827da2 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts @@ -5,12 +5,14 @@ import type { ILoginRequest, ILoginResponse, ILogoutRequest, + IPasswordPolicyResponse, + IPasswordOptions, IRefreshTokenRequest, IRefreshTokenResponse, IUpdatePasswordRequest } from "@3sixty/api-auth-entity-storage-models"; import { BaseRestClient } from "@3sixty/api-core"; -import type { INoContentResponse } from "@3sixty/api-models"; +import type { INoContentRequest, INoContentResponse } from "@3sixty/api-models"; import { Guards } from "@3sixty/core"; import { nameof } from "@3sixty/nameof"; import { CookieHelper, HeaderTypes } from "@3sixty/web"; @@ -160,4 +162,14 @@ export class EntityStorageAuthenticationRestClient } }); } + + /** + * Get the password policy used for password validation. + * @returns The password policy options. + */ + public async passwordPolicy(): Promise { + const response = await this.fetch("/policy", "GET"); + + return response.body; + } } diff --git a/packages/api-auth-entity-storage-rest-client/src/index.ts b/packages/api-auth-entity-storage-rest-client/src/index.ts index c012288..49c8558 100644 --- a/packages/api-auth-entity-storage-rest-client/src/index.ts +++ b/packages/api-auth-entity-storage-rest-client/src/index.ts @@ -1,6 +1,5 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. export * from "./entityStorageAuthenticationAdminRestClient.js"; -export * from "./entityStorageAuthenticationAuditRestClient.js"; export * from "./entityStorageAuthenticationRestClient.js"; export * from "./models/entityStorageAuthenticationRestClientConstructorOptions.js"; diff --git a/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAdminRestClient.spec.ts b/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAdminRestClient.spec.ts index e0bf060..41a134f 100644 --- a/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAdminRestClient.spec.ts +++ b/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAdminRestClient.spec.ts @@ -17,8 +17,7 @@ const PREFIX = "authentication/admin"; const TEST_USER = { email: "user@example.com", userIdentity: "did:example:123456789abcdefghi", - organizationIdentity: "did:example:123456789abcdefghi", - scope: ["scope1", "scope2"] + organizationIdentity: "did:example:123456789abcdefghi" }; const fetchMock = vi.fn(); diff --git a/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationRestClient.spec.ts b/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationRestClient.spec.ts index e727a57..630fd1e 100644 --- a/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationRestClient.spec.ts +++ b/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationRestClient.spec.ts @@ -189,4 +189,28 @@ describe("EntityStorageAuthenticationRestClient", () => { ).resolves.toBeUndefined(); }); }); + + describe("passwordPolicy", () => { + test("sends GET to /policy", async () => { + fetchMock.mockResolvedValueOnce( + jsonResponse({ minPasswordLength: 15, maxPasswordLength: 128 }) + ); + + await client.passwordPolicy(); + + const [url, options] = fetchMock.mock.calls[0]; + expect(url).toBe(`${ENDPOINT}/${PREFIX}/policy`); + expect(options.method).toBe(HttpMethod.GET); + }); + + test("returns the policy from the response body", async () => { + fetchMock.mockResolvedValueOnce( + jsonResponse({ minPasswordLength: 15, maxPasswordLength: 128 }) + ); + + const result = await client.passwordPolicy(); + + expect(result).toEqual({ minPasswordLength: 15, maxPasswordLength: 128 }); + }); + }); }); diff --git a/packages/api-auth-entity-storage-service/docs/examples.md b/packages/api-auth-entity-storage-service/docs/examples.md index 2c815e9..65a5608 100644 --- a/packages/api-auth-entity-storage-service/docs/examples.md +++ b/packages/api-auth-entity-storage-service/docs/examples.md @@ -15,19 +15,18 @@ await adminService.create({ email: 'owner@example.org', password: 'StartPassword123', userIdentity: 'did:example:owner', - organizationIdentity: 'did:example:org', - scope: ['admin'] + organizationIdentity: 'did:example:org' }); await adminService.update({ email: 'owner@example.org', - scope: ['admin', 'security'] + organizationIdentity: 'did:example:org:security' }); await adminService.updatePassword('owner@example.org', 'StartPassword124', 'StartPassword123'); const fromIdentity = await adminService.getByIdentity('did:example:owner'); -console.log(fromIdentity.scope.length); // 2 +console.log(fromIdentity.organizationIdentity); // did:example:org:security ``` ```typescript @@ -50,6 +49,9 @@ const authService = new EntityStorageAuthenticationService(); await authService.start('default'); console.log(authService.className()); // EntityStorageAuthenticationService +const policy = await authService.passwordPolicy(); +console.log(policy.minPasswordLength); // 8 + const loginResult = await authService.login('alice@example.org', 'correct-horse-battery-staple'); const refreshResult = await authService.refresh(loginResult.token); @@ -58,96 +60,8 @@ await authService.updatePassword('correct-horse-battery-staple', 'correct-horse- await authService.logout(refreshResult.token); console.log(refreshResult.expiry > 0); // true -``` - -## EntityStorageAuthenticationRateService - -```typescript -import { EntityStorageAuthenticationRateService } from '@3sixty/api-auth-entity-storage-service'; -import { BaseError, GeneralError } from '@3sixty/core'; -import { TooManyRequestsError } from '@3sixty/api-models'; - -const rateService = new EntityStorageAuthenticationRateService({ - config: { - cleanupIntervalMinutes: 5 - } -}); - -console.log(rateService.className()); // EntityStorageAuthenticationRateService - -await rateService.registerAction('login', { - maxAttempts: 3, - windowMinutes: 15 -}); - -await rateService.start('default'); - -await rateService.check('login', 'alice@example.org'); -await rateService.check('login', 'alice@example.org'); -await rateService.check('login', 'alice@example.org'); - -try { - await rateService.check('login', 'alice@example.org'); -} catch (error) { - if (BaseError.isErrorName(error, TooManyRequestsError.CLASS_NAME)) { - const tooMany = error as TooManyRequestsError; - console.log(tooMany.properties?.retryAfterSeconds); // 900 - console.log(tooMany.properties?.nextRequestTime); // 2026-04-13T10:15:00.000Z - } -} - -await rateService.clear('login', 'alice@example.org'); -await rateService.unregisterAction('login'); -try { - await rateService.check('login', 'alice@example.org'); -} catch (error) { - if (BaseError.isErrorName(error, GeneralError.CLASS_NAME)) { - console.log((error as GeneralError).name); // GeneralError - } -} - -await rateService.stop('default'); -``` - -## EntityStorageAuthenticationAuditService - -```typescript -import { EntityStorageAuthenticationAuditService } from '@3sixty/api-auth-entity-storage-service'; - -const auditService = new EntityStorageAuthenticationAuditService({ - config: { - ipHashSalt: 'StrongServerSideSaltForAuditHashing123' - } -}); - -console.log(auditService.className()); // EntityStorageAuthenticationAuditService - -const createdAuditId = await auditService.create({ - event: 'login-failure', - actorId: 'did:example:user:alice', - nodeId: 'did:example:node:eu-west-1', - organizationId: 'did:example:org:core', - tenantId: 'did:example:tenant:alpha', - data: { - reason: 'invalid-password' - } -}); - -const auditPage = await auditService.query( - { - actorId: 'did:example:user:alice', - event: 'login-failure', - startDate: '2026-04-01T00:00:00.000Z', - endDate: '2026-04-30T23:59:59.999Z' - }, - undefined, - 50 -); - -console.log(createdAuditId); // 018f2f67bb9d4a0caad8386f56df85ce -console.log(auditPage.entries.length); // 1 -console.log(auditPage.cursor); // eyJpZCI6IjAxOGYyZjY3YmI5ZDRhMGNhYWQ4Mzg2ZjU2ZGY4NWNlIn0= +await authService.stop('default'); ``` ## AuthHeaderProcessor @@ -175,4 +89,6 @@ const processorState: { [id: string]: unknown } = {}; await processor.pre(request, response, { skipAuth: false }, contextIds, processorState); await processor.post(request, response, { skipAuth: false }, contextIds, processorState); + +await processor.stop(); ``` diff --git a/packages/api-auth-entity-storage-service/docs/open-api/spec.json b/packages/api-auth-entity-storage-service/docs/open-api/spec.json index c32b9ca..4ef52da 100644 --- a/packages/api-auth-entity-storage-service/docs/open-api/spec.json +++ b/packages/api-auth-entity-storage-service/docs/open-api/spec.json @@ -22,10 +22,6 @@ { "name": "Authentication Admin", "description": "Authentication Admin endpoints for the REST server." - }, - { - "name": "Authentication Audit", - "description": "Authentication audit endpoints for the REST server." } ], "paths": { @@ -463,11 +459,7 @@ "email": "user@example.com", "password": "MyPassword123!", "userIdentity": "did:example:123456789abcdefghi", - "organizationIdentity": "did:example:123456789abcdefghi", - "scope": [ - "scope1", - "scope2" - ] + "organizationIdentity": "did:example:123456789abcdefghi" } } } @@ -576,11 +568,7 @@ "summary": "The request to update an existing user.", "value": { "userIdentity": "did:example:123456789abcdefghi", - "organizationIdentity": "did:example:123456789abcdefghi", - "scope": [ - "scope1", - "scope2" - ] + "organizationIdentity": "did:example:123456789abcdefghi" } } } @@ -688,11 +676,7 @@ "value": { "email": "user@example.com", "userIdentity": "did:example:123456789abcdefghi", - "organizationIdentity": "did:example:123456789abcdefghi", - "scope": [ - "scope1", - "scope2" - ] + "organizationIdentity": "did:example:123456789abcdefghi" } } } @@ -993,11 +977,7 @@ "value": { "email": "user@example.com", "userIdentity": "did:example:123456789abcdefghi", - "organizationIdentity": "did:example:123456789abcdefghi", - "scope": [ - "scope1", - "scope2" - ] + "organizationIdentity": "did:example:123456789abcdefghi" } } } @@ -1063,935 +1043,79 @@ } } } - }, - "/authentication/audit": { - "post": { - "operationId": "authenticationAuditCreate", - "summary": "Create an authentication audit entry", - "tags": [ - "Authentication Audit" + } + }, + "components": { + "schemas": { + "AdminUserCreateRequest": { + "type": "object", + "properties": { + "password": { + "type": "string" + } + }, + "required": [ + "password" ], - "security": [ + "allOf": [ { - "jwtBearerAuthScheme": [] + "$ref": "#/components/schemas/AuthenticationUser" } ], - "requestBody": { - "description": "Create an authentication audit entry.", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuditCreateRequest" - }, - "examples": { - "authenticationAuditCreateRequestExample": { - "summary": "The request to create an authentication audit entry.", - "value": { - "actorId": "user@example.com", - "event": "login-success", - "data": { - "organizationIdentity": "did:example:org1" - } - } - } - } - } + "description": "The body of the request." + }, + "AdminUserUpdatePasswordRequest": { + "type": "object", + "properties": { + "newPassword": { + "type": "string", + "description": "The new password for the user." + }, + "currentPassword": { + "type": "string", + "description": "The current password for the user." } }, - "responses": { - "201": { - "description": "The rest request ended in created response.", - "headers": { - "location": { - "schema": { - "type": "string" - }, - "description": "e.g. 018f0b53d5d5704fa3a06d6ed2478575" - } - } - }, - "400": { - "description": "The server cannot process the request, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "GeneralError", - "message": "errorMessage", - "properties": { - "foo": "bar" - } - } - } - } - } - } - }, - "401": { - "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "UnauthorizedError", - "message": "errorMessage" - } - } - } - } - } + "required": [ + "newPassword" + ], + "description": "The body of the request." + }, + "AdminUserUpdateRequest": { + "description": "The body of the request.", + "type": "object", + "properties": { + "userIdentity": { + "type": "string", + "description": "The user identity." }, - "500": { - "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "InternalServerError", - "message": "errorMessage" - } - } - } - } - } + "organizationIdentity": { + "type": "string", + "description": "The users organization." } } }, - "get": { - "operationId": "authenticationAuditQuery", - "summary": "Query authentication audit entries", - "tags": [ - "Authentication Audit" - ], - "parameters": [ - { - "name": "actorId", - "description": "The actor identifier to filter by.", - "in": "query", - "required": false, - "schema": { - "type": "string" - }, - "example": "user@example.com" - }, - { - "name": "organizationId", - "description": "The organization identifier to filter by.", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "tenantId", - "description": "The tenant identifier to filter by.", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "nodeId", - "description": "The node identifier to filter by.", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "event", - "description": "The event to filter by.", - "in": "query", - "required": false, - "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/AuthAuditEvent" - }, - { - "type": "string" - } - ] - }, - "example": "login-success" - }, - { - "name": "startDate", - "description": "The inclusive start date for filtering, in ISO 8601 format.", - "in": "query", - "required": false, - "schema": { - "type": "string" - }, - "example": "2026-01-01T00:00:00.000Z" - }, - { - "name": "endDate", - "description": "The inclusive end date for filtering, in ISO 8601 format.", - "in": "query", - "required": false, - "schema": { - "type": "string" - }, - "example": "2026-01-31T23:59:59.999Z" - }, - { - "name": "cursor", - "description": "The pagination cursor.", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "description": "The maximum number of results to return.", - "in": "query", - "required": false, - "schema": { - "type": "string" - }, - "example": "50" - } - ], - "security": [ - { - "jwtBearerAuthScheme": [] - } - ], - "responses": { - "200": { - "description": "Response from querying authentication audit entries.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuditQueryResponse" - }, - "examples": { - "authenticationAuditQueryResponseExample": { - "summary": "The response for querying authentication audit entries.", - "value": { - "entries": [ - { - "id": "018f0b53d5d5704fa3a06d6ed2478575", - "actorId": "user@example.com", - "dateCreated": "2026-01-12T09:05:23.123Z", - "event": "login-success", - "data": { - "organizationIdentity": "did:example:org1" - } - } - ], - "cursor": "next-cursor" - } - } - } - } - } - }, - "400": { - "description": "The server cannot process the request, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "GeneralError", - "message": "errorMessage", - "properties": { - "foo": "bar" - } - } - } - } - } - } + "AuthenticationUser": { + "description": "Contract definition for authentication user.", + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "The user e-mail address." }, - "401": { - "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "UnauthorizedError", - "message": "errorMessage" - } - } - } - } - } - }, - "500": { - "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "InternalServerError", - "message": "errorMessage" - } - } - } - } - } - } - } - } - }, - "/authentication/audit/{id}": { - "get": { - "operationId": "authenticationAuditGet", - "summary": "Get an authentication audit entry", - "tags": [ - "Authentication Audit" - ], - "parameters": [ - { - "name": "id", - "description": "The audit entry id.", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple", - "example": "018f0b53d5d5704fa3a06d6ed2478575" - } - ], - "security": [ - { - "jwtBearerAuthScheme": [] - } - ], - "responses": { - "200": { - "description": "Response from getting an authentication audit entry.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuthenticationAuditEntry" - }, - "examples": { - "authenticationAuditGetResponseExample": { - "summary": "The response for getting an authentication audit entry.", - "value": { - "id": "018f0b53d5d5704fa3a06d6ed2478575", - "actorId": "user@example.com", - "dateCreated": "2026-01-12T09:05:23.123Z", - "event": "login-success", - "data": { - "organizationIdentity": "did:example:org1" - } - } - } - } - } - } - }, - "400": { - "description": "The server cannot process the request, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "GeneralError", - "message": "errorMessage", - "properties": { - "foo": "bar" - } - } - } - } - } - } - }, - "401": { - "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "UnauthorizedError", - "message": "errorMessage" - } - } - } - } - } - }, - "500": { - "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "InternalServerError", - "message": "errorMessage" - } - } - } - } - } - } - } - }, - "put": { - "operationId": "authenticationAuditUpdate", - "summary": "Update an authentication audit entry", - "tags": [ - "Authentication Audit" - ], - "parameters": [ - { - "name": "id", - "description": "The audit entry id.", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple", - "example": "018f0b53d5d5704fa3a06d6ed2478575" - } - ], - "security": [ - { - "jwtBearerAuthScheme": [] - } - ], - "requestBody": { - "description": "Update an authentication audit entry.", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AuditUpdateRequest" - }, - "examples": { - "authenticationAuditUpdateRequestExample": { - "summary": "The request to update an authentication audit entry.", - "value": { - "data": { - "organizationIdentity": "did:example:org1" - } - } - } - } - } - } - }, - "responses": { - "204": { - "description": "The rest request ended in success with no data." - }, - "400": { - "description": "The server cannot process the request, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "GeneralError", - "message": "errorMessage", - "properties": { - "foo": "bar" - } - } - } - } - } - } - }, - "401": { - "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "UnauthorizedError", - "message": "errorMessage" - } - } - } - } - } - }, - "500": { - "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "InternalServerError", - "message": "errorMessage" - } - } - } - } - } - } - } - }, - "delete": { - "operationId": "authenticationAuditRemove", - "summary": "Remove an authentication audit entry", - "tags": [ - "Authentication Audit" - ], - "parameters": [ - { - "name": "id", - "description": "The audit entry id.", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "style": "simple", - "example": "018f0b53d5d5704fa3a06d6ed2478575" - } - ], - "security": [ - { - "jwtBearerAuthScheme": [] - } - ], - "responses": { - "204": { - "description": "The rest request ended in success with no data." - }, - "400": { - "description": "The server cannot process the request, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "GeneralError", - "message": "errorMessage", - "properties": { - "foo": "bar" - } - } - } - } - } - } - }, - "401": { - "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "UnauthorizedError", - "message": "errorMessage" - } - } - } - } - } - }, - "500": { - "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - }, - "examples": { - "exampleResponse": { - "value": { - "name": "InternalServerError", - "message": "errorMessage" - } - } - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "AdminUserCreateRequest": { - "type": "object", - "properties": { - "password": { - "type": "string" - } - }, - "required": [ - "password" - ], - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationUser" - } - ], - "description": "The body of the request." - }, - "AdminUserUpdatePasswordRequest": { - "type": "object", - "properties": { - "newPassword": { - "type": "string", - "description": "The new password for the user." - }, - "currentPassword": { - "type": "string", - "description": "The current password for the user." - } - }, - "required": [ - "newPassword" - ], - "description": "The body of the request." - }, - "AdminUserUpdateRequest": { - "description": "The body of the request.", - "type": "object", - "properties": { - "userIdentity": { - "type": "string", - "description": "The user identity." - }, - "organizationIdentity": { - "type": "string", - "description": "The users organization." - }, - "scope": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The scope assigned to the user, comma separated." - } - } - }, - "AuditCreateRequest": { - "description": "The body of the request.", - "type": "object", - "properties": { - "event": { - "anyOf": [ - { - "$ref": "#/components/schemas/AuthAuditEvent" - }, - { - "type": "string" - } - ], - "description": "The audit event that occurred." - }, - "actorId": { - "type": "string", - "description": "The actor identifier, could be e-mail, username, or other unique identifier." - }, - "nodeId": { - "type": "string", - "description": "The node identifier associated with the audit entry, if applicable." - }, - "organizationId": { - "type": "string", - "description": "The organization identifier associated with the audit entry, if applicable." - }, - "tenantId": { - "type": "string", - "description": "The tenant identifier associated with the audit entry, if applicable." - }, - "ipAddressHashes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The hashed IP addresses of the client." - }, - "userAgent": { - "type": "string", - "description": "The user agent string of the client." - }, - "correlationId": { - "type": "string", - "description": "The correlation ID for request tracing." - }, - "data": { - "description": "Additional data related to the audit entry, such as IP address, user agent, etc." - } - }, - "required": [ - "event" - ] - }, - "AuditQueryResponse": { - "type": "object", - "properties": { - "entries": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AuthenticationAuditEntry" - }, - "description": "The returned audit entries." - }, - "cursor": { - "type": "string", - "description": "The cursor to retrieve the next page, if any." - } - }, - "required": [ - "entries" - ], - "description": "The response body." - }, - "AuditUpdateRequest": { - "description": "The body of the request.", - "type": "object", - "properties": { - "event": { - "anyOf": [ - { - "$ref": "#/components/schemas/AuthAuditEvent" - }, - { - "type": "string" - } - ], - "description": "The audit event that occurred." - }, - "actorId": { - "type": "string", - "description": "The actor identifier, could be e-mail, username, or other unique identifier." - }, - "nodeId": { - "type": "string", - "description": "The node identifier associated with the audit entry, if applicable." - }, - "organizationId": { - "type": "string", - "description": "The organization identifier associated with the audit entry, if applicable." - }, - "tenantId": { - "type": "string", - "description": "The tenant identifier associated with the audit entry, if applicable." - }, - "ipAddressHashes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The hashed IP addresses of the client." - }, - "userAgent": { - "type": "string", - "description": "The user agent string of the client." - }, - "correlationId": { - "type": "string", - "description": "The correlation ID for request tracing." - }, - "data": { - "description": "Additional data related to the audit entry, such as IP address, user agent, etc." - } - } - }, - "AuthAuditEvent": { - "description": "Supported authentication audit event values.", - "anyOf": [ - { - "const": "login-success", - "description": "Login success." - }, - { - "const": "login-failure", - "description": "Login failure." - }, - { - "const": "logout", - "description": "Logout." - }, - { - "const": "token-refreshed", - "description": "Token refreshed." - }, - { - "const": "account-created", - "description": "Account created." - }, - { - "const": "account-deleted", - "description": "Account deleted." - }, - { - "const": "account-updated", - "description": "Account updated." - }, - { - "const": "account-locked", - "description": "Account locked." - }, - { - "const": "account-unlocked", - "description": "Account unlocked." - }, - { - "const": "password-changed", - "description": "Password changed." - } - ] - }, - "AuthenticationAuditEntry": { - "description": "Contract definition for authentication audit entry.", - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The unique identifier for the audit entry." - }, - "event": { - "anyOf": [ - { - "$ref": "#/components/schemas/AuthAuditEvent" - }, - { - "type": "string" - } - ], - "description": "The audit event that occurred." - }, - "dateCreated": { - "type": "string", - "description": "The timestamp of the audit entry in ISO 8601 format." - }, - "actorId": { - "type": "string", - "description": "The actor identifier, could be e-mail, username, or other unique identifier." - }, - "nodeId": { - "type": "string", - "description": "The node identifier associated with the audit entry, if applicable." - }, - "organizationId": { - "type": "string", - "description": "The organization identifier associated with the audit entry, if applicable." - }, - "tenantId": { - "type": "string", - "description": "The tenant identifier associated with the audit entry, if applicable." - }, - "ipAddressHashes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The hashed IP addresses of the client." - }, - "userAgent": { - "type": "string", - "description": "The user agent string of the client." - }, - "correlationId": { - "type": "string", - "description": "The correlation ID for request tracing." - }, - "data": { - "description": "Additional data related to the audit entry, such as IP address, user agent, etc." - } - }, - "required": [ - "id", - "event", - "dateCreated" - ] - }, - "AuthenticationUser": { - "description": "Contract definition for authentication user.", - "type": "object", - "properties": { - "email": { - "type": "string", - "description": "The user e-mail address." - }, - "userIdentity": { - "type": "string", - "description": "The user identity." + "userIdentity": { + "type": "string", + "description": "The user identity." }, "organizationIdentity": { "type": "string", "description": "The users organization." - }, - "scope": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The scope assigned to the user, comma separated." } }, "required": [ "email", "userIdentity", - "organizationIdentity", - "scope" + "organizationIdentity" ] }, "Error": { diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationUser.md b/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationUser.md index 4bb1f9a..77026f8 100644 --- a/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationUser.md +++ b/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationUser.md @@ -54,14 +54,6 @@ The users organization. *** -### scope {#scope} - -> **scope**: `string` - -The scope assigned to the user, comma separated. - -*** - ### passwordVersion? {#passwordversion} > `optional` **passwordVersion?**: `number` diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/TokenHelper.md b/packages/api-auth-entity-storage-service/docs/reference/classes/TokenHelper.md index 6f86bbb..8ecfb1a 100644 --- a/packages/api-auth-entity-storage-service/docs/reference/classes/TokenHelper.md +++ b/packages/api-auth-entity-storage-service/docs/reference/classes/TokenHelper.md @@ -24,7 +24,7 @@ Runtime name for the class. ### createToken() {#createtoken} -> `static` **createToken**(`vaultConnector`, `nodeId`, `signingKeyName`, `userIdentity`, `organizationIdentity`, `tenantId`, `ttlMinutes`, `scope?`, `passwordVersion?`): `Promise`\<\{ `token`: `string`; `expiry`: `number`; \}\> +> `static` **createToken**(`vaultConnector`, `nodeId`, `signingKeyName`, `userIdentity`, `organizationIdentity`, `tenantId`, `ttlMinutes`, `passwordVersion?`): `Promise`\<\{ `token`: `string`; `expiry`: `number`; \}\> Create a new token. @@ -72,12 +72,6 @@ The tenant id for the token. The time to live for the token in minutes. -##### scope? - -`string` - -The scopes for the token. - ##### passwordVersion? `number` @@ -94,7 +88,7 @@ The new token and its expiry date. ### verify() {#verify} -> `static` **verify**(`vaultConnector`, `nodeId`, `signingKeyName`, `token`, `requiredScopes?`, `verifyUser?`): `Promise`\<\{ `header`: `JWTHeaderParameters`; `payload`: `JWTPayload`; \}\> +> `static` **verify**(`vaultConnector`, `nodeId`, `signingKeyName`, `token`, `verifyUser?`): `Promise`\<\{ `header`: `JWTHeaderParameters`; `payload`: `JWTPayload`; \}\> Verify the token. @@ -124,12 +118,6 @@ The signing key name, expected to match the JWT key identifier. The token to verify. -##### requiredScopes? - -`string`[] - -The required scopes. - ##### verifyUser? (`sub`, `org`, `tid`, `passwordVersion`) => `Promise`\<`string`[]\> diff --git a/packages/api-auth-entity-storage-service/docs/reference/index.md b/packages/api-auth-entity-storage-service/docs/reference/index.md index 3022c6e..81636d8 100644 --- a/packages/api-auth-entity-storage-service/docs/reference/index.md +++ b/packages/api-auth-entity-storage-service/docs/reference/index.md @@ -2,13 +2,9 @@ ## Classes -- [AuthenticationAuditEntry](classes/AuthenticationAuditEntry.md) -- [AuthenticationRateEntry](classes/AuthenticationRateEntry.md) - [AuthenticationUser](classes/AuthenticationUser.md) - [AuthHeaderProcessor](classes/AuthHeaderProcessor.md) - [EntityStorageAuthenticationAdminService](classes/EntityStorageAuthenticationAdminService.md) -- [EntityStorageAuthenticationAuditService](classes/EntityStorageAuthenticationAuditService.md) -- [EntityStorageAuthenticationRateService](classes/EntityStorageAuthenticationRateService.md) - [EntityStorageAuthenticationService](classes/EntityStorageAuthenticationService.md) - [PasswordHelper](classes/PasswordHelper.md) - [TokenHelper](classes/TokenHelper.md) @@ -19,10 +15,6 @@ - [IAuthHeaderProcessorConstructorOptions](interfaces/IAuthHeaderProcessorConstructorOptions.md) - [IEntityStorageAuthenticationAdminServiceConfig](interfaces/IEntityStorageAuthenticationAdminServiceConfig.md) - [IEntityStorageAuthenticationAdminServiceConstructorOptions](interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md) -- [IEntityStorageAuthenticationAuditServiceConfig](interfaces/IEntityStorageAuthenticationAuditServiceConfig.md) -- [IEntityStorageAuthenticationAuditServiceConstructorOptions](interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md) -- [IEntityStorageAuthenticationRateServiceConfig](interfaces/IEntityStorageAuthenticationRateServiceConfig.md) -- [IEntityStorageAuthenticationRateServiceConstructorOptions](interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md) - [IEntityStorageAuthenticationServiceConfig](interfaces/IEntityStorageAuthenticationServiceConfig.md) - [IEntityStorageAuthenticationServiceConstructorOptions](interfaces/IEntityStorageAuthenticationServiceConstructorOptions.md) @@ -30,7 +22,6 @@ - [restEntryPoints](variables/restEntryPoints.md) - [tagsAuthenticationAdmin](variables/tagsAuthenticationAdmin.md) -- [tagsAuthenticationAudit](variables/tagsAuthenticationAudit.md) - [tagsAuthentication](variables/tagsAuthentication.md) ## Functions @@ -42,12 +33,6 @@ - [authenticationAdminGetUser](functions/authenticationAdminGetUser.md) - [authenticationAdminGetUserByIdentity](functions/authenticationAdminGetUserByIdentity.md) - [authenticationAdminRemoveUser](functions/authenticationAdminRemoveUser.md) -- [generateRestRoutesAuthenticationAudit](functions/generateRestRoutesAuthenticationAudit.md) -- [authenticationAuditCreate](functions/authenticationAuditCreate.md) -- [authenticationAuditGet](functions/authenticationAuditGet.md) -- [authenticationAuditUpdate](functions/authenticationAuditUpdate.md) -- [authenticationAuditRemove](functions/authenticationAuditRemove.md) -- [authenticationAuditQuery](functions/authenticationAuditQuery.md) - [generateRestRoutesAuthentication](functions/generateRestRoutesAuthentication.md) - [authenticationLogin](functions/authenticationLogin.md) - [authenticationLogout](functions/authenticationLogout.md) diff --git a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md b/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md index a0f47de..d30fb0a 100644 --- a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md +++ b/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAdminServiceConstructorOptions.md @@ -22,7 +22,7 @@ authentication-user > `optional` **authenticationAuditServiceType?**: `string` -The audit service. +The audit component. #### Default diff --git a/packages/api-auth-entity-storage-service/locales/en.json b/packages/api-auth-entity-storage-service/locales/en.json index eceae72..bd86eb0 100644 --- a/packages/api-auth-entity-storage-service/locales/en.json +++ b/packages/api-auth-entity-storage-service/locales/en.json @@ -13,7 +13,8 @@ "updateUserFailed": "Updating the user failed", "removeUserFailed": "Removing the user failed", "updatePasswordFailed": "Updating the user's password failed", - "userNotFound": "The user with the specified e-mail could not be found \"{notFoundId}\"" + "userNotFound": "The user with the specified e-mail could not be found \"{notFoundId}\"", + "insufficientRoleForEscalatedTarget": "Caller does not hold the required role \"{roleName}\" to modify an account with escalated privilege." }, "authHeaderProcessor": { "nodeIdNotSet": "The node ID has not been set" @@ -21,16 +22,6 @@ "passwordHelper": { "currentPasswordMismatch": "The current password is incorrect" }, - "entityStorageAuthenticationAuditService": { - "auditEntryNotFound": "The audit entry with the specified identifier could not be found \"{notFoundId}\"", - "getAuditEntryFailed": "Getting the audit entry failed", - "updateAuditEntryFailed": "Updating the audit entry failed", - "removeAuditEntryFailed": "Removing the audit entry failed" - }, - "entityStorageAuthenticationRateService": { - "actionConfigMissing": "No rate-limit configuration exists for action \"{action}\".", - "rateLimitExceeded": "The rate limit for action \"{action}\" has been exceeded. Retry after {retryAfterSeconds} seconds." - }, "tokenHelper": { "missing": "The JSON Web token could not be found in the authorization header", "headerKeyIdMismatch": "The JSON Web token key identifier does not match the expected signing key", @@ -41,9 +32,6 @@ "userNotVerified": "The user associated with the JSON Web token could not be verified", "organizationNotVerified": "The organization associated with the JSON Web token could not be verified", "tenantNotVerified": "The tenant associated with the JSON Web token could not be verified" - }, - "validation": { - "saltEntropyTooLow": "Salt must have at least 8 unique characters for sufficient entropy." } } } diff --git a/packages/api-auth-entity-storage-service/package.json b/packages/api-auth-entity-storage-service/package.json index f39269d..3464c4a 100644 --- a/packages/api-auth-entity-storage-service/package.json +++ b/packages/api-auth-entity-storage-service/package.json @@ -30,15 +30,14 @@ }, "dependencies": { "@3sixty/api-auth-entity-storage-models": "0.20.0-next.0", - "@3sixty/api-core": "next", + "@3sixty/api-auth-models": "0.20.0-next.0", "@3sixty/api-models": "next", - "@3sixty/background-task-models": "next", + "@3sixty/authorization-models": "next", "@3sixty/context": "next", "@3sixty/core": "next", "@3sixty/crypto": "next", "@3sixty/entity": "next", "@3sixty/entity-storage-models": "next", - "@3sixty/logging-models": "next", "@3sixty/nameof": "next", "@3sixty/vault-models": "next", "@3sixty/web": "next" diff --git a/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts b/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts index a9450a6..20abb49 100644 --- a/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts +++ b/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts @@ -5,12 +5,12 @@ import { entity, property } from "@3sixty/entity"; /** * Class defining the storage for user login credentials. */ -@entity() +@entity({ version: 1 }) export class AuthenticationUser { /** * The user e-mail address. */ - @property({ type: "string", isPrimary: true }) + @property({ type: "string", format: "email", isPrimary: true }) public email!: string; /** @@ -28,21 +28,15 @@ export class AuthenticationUser { /** * The user identity. */ - @property({ type: "string", isSecondary: true }) + @property({ type: "string", maxLength: 255, isSecondary: true }) public identity!: string; /** * The users organization. */ - @property({ type: "string" }) + @property({ type: "string", maxLength: 255 }) public organization!: string; - /** - * The scope assigned to the user, comma separated. - */ - @property({ type: "string" }) - public scope!: string; - /** * The password version counter, incremented on every password change to invalidate existing tokens. */ diff --git a/packages/api-auth-entity-storage-service/src/entities/authenticationUserV0.ts b/packages/api-auth-entity-storage-service/src/entities/authenticationUserV0.ts new file mode 100644 index 0000000..f11d447 --- /dev/null +++ b/packages/api-auth-entity-storage-service/src/entities/authenticationUserV0.ts @@ -0,0 +1,51 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { entity, property } from "@3sixty/entity"; + +/** + * Class defining the storage for user login credentials. + */ +@entity({ version: 0 }) +export class AuthenticationUserV0 { + /** + * The user e-mail address. + */ + @property({ type: "string", isPrimary: true }) + public email!: string; + + /** + * The encrypted password for the user. + */ + @property({ type: "string" }) + public password!: string; + + /** + * The salt for the password. + */ + @property({ type: "string" }) + public salt!: string; + + /** + * The user identity. + */ + @property({ type: "string", isSecondary: true }) + public identity!: string; + + /** + * The users organization. + */ + @property({ type: "string" }) + public organization!: string; + + /** + * The scope assigned to the user, comma separated. + */ + @property({ type: "string" }) + public scope!: string; + + /** + * The password version counter, incremented on every password change to invalidate existing tokens. + */ + @property({ type: "integer", optional: true }) + public passwordVersion?: number; +} diff --git a/packages/api-auth-entity-storage-service/src/index.ts b/packages/api-auth-entity-storage-service/src/index.ts index 99186ef..10132b6 100644 --- a/packages/api-auth-entity-storage-service/src/index.ts +++ b/packages/api-auth-entity-storage-service/src/index.ts @@ -1,27 +1,20 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -export * from "./entities/authenticationAuditEntry.js"; -export * from "./entities/authenticationRateEntry.js"; export * from "./entities/authenticationUser.js"; +export * from "./entities/authenticationUserV0.js"; export * from "./models/IAuthHeaderProcessorConfig.js"; export * from "./models/IAuthHeaderProcessorConstructorOptions.js"; +export * from "./models/IAuthTokenContext.js"; export * from "./models/IEntityStorageAuthenticationAdminServiceConfig.js"; export * from "./models/IEntityStorageAuthenticationAdminServiceConstructorOptions.js"; -export * from "./models/IEntityStorageAuthenticationAuditServiceConfig.js"; -export * from "./models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js"; -export * from "./models/IEntityStorageAuthenticationRateServiceConfig.js"; -export * from "./models/IEntityStorageAuthenticationRateServiceConstructorOptions.js"; export * from "./models/IEntityStorageAuthenticationServiceConfig.js"; export * from "./models/IEntityStorageAuthenticationServiceConstructorOptions.js"; export * from "./processors/authHeaderProcessor.js"; export * from "./restEntryPoints.js"; export * from "./routes/entityStorageAuthenticationAdminRoutes.js"; -export * from "./routes/entityStorageAuthenticationAuditRoutes.js"; export * from "./routes/entityStorageAuthenticationRoutes.js"; export * from "./schema.js"; export * from "./services/entityStorageAuthenticationAdminService.js"; -export * from "./services/entityStorageAuthenticationAuditService.js"; -export * from "./services/entityStorageAuthenticationRateService.js"; export * from "./services/entityStorageAuthenticationService.js"; export * from "./utils/tokenHelper.js"; export * from "./utils/passwordHelper.js"; diff --git a/packages/api-auth-entity-storage-service/src/models/IAuthHeaderProcessorConfig.ts b/packages/api-auth-entity-storage-service/src/models/IAuthHeaderProcessorConfig.ts index ecf6570..495534b 100644 --- a/packages/api-auth-entity-storage-service/src/models/IAuthHeaderProcessorConfig.ts +++ b/packages/api-auth-entity-storage-service/src/models/IAuthHeaderProcessorConfig.ts @@ -21,4 +21,26 @@ export interface IAuthHeaderProcessorConfig { * Include the stack with errors. */ includeErrorStack?: boolean; + + /** + * The time in milliseconds a verified token is kept in the in-memory cache, counted from when it + * was verified and not extended by use, set to 0 to disable caching. A hit skips the vault + * signature check, the tenant lookup and the user lookup, so this is also the longest a password + * change, a user removal or a signing key rotation can go unnoticed. An entry never outlives the + * expiry claim of its own token either. + * @default 30000 + */ + tokenCacheTtlMs?: number; + + /** + * The maximum number of verified tokens to hold in the in-memory cache. + * @default undefined (LfuCache default) + */ + tokenCacheCapacity?: number; + + /** + * Maximum time in milliseconds to wait for the token cache mutex during a cache population. + * @default undefined (LfuCache default) + */ + tokenCacheMutexTimeoutMs?: number; } diff --git a/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts b/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts new file mode 100644 index 0000000..013521f --- /dev/null +++ b/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts @@ -0,0 +1,38 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +/** + * The context resolved from an access token, everything a request needs that does not depend on + * the route being called. + */ +export interface IAuthTokenContext { + /** + * The tenant id from the token, if it carried one. + */ + tenantId?: string; + + /** + * The organization id of the tenant the token belongs to. + */ + tenantOrganizationId?: string; + + /** + * The public origin of the tenant the token belongs to. + */ + tenantPublicOrigin?: string; + + /** + * The identity of the verified user. + */ + userIdentity?: string; + + /** + * The organization of the verified user. + */ + userOrganization?: string; + + /** + * The expiry of the token in milliseconds since the epoch, if it carried one. + */ + expires?: number; +} diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConfig.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConfig.ts index 14b5354..b0d6885 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConfig.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConfig.ts @@ -7,7 +7,25 @@ export interface IEntityStorageAuthenticationAdminServiceConfig { /** * The minimum password length. - * @default 8 + * @default 15 */ minPasswordLength?: number; + + /** + * The maximum password length. + * @default 128 + */ + maxPasswordLength?: number; + + /** + * The role that grants escalated privilege, only callers holding it can modify accounts that hold it. + * @default global-admin + */ + escalatedPrivilegeRole?: string; + + /** + * The authorization model ID used to look up roles. + * @default system + */ + authorizationModelId?: string; } diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.ts index f9860e2..8a8fdf9 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAdminServiceConstructorOptions.ts @@ -13,11 +13,17 @@ export interface IEntityStorageAuthenticationAdminServiceConstructorOptions { userEntityStorageType?: string; /** - * The audit service. + * The audit component. * @default authentication-audit */ authenticationAuditServiceType?: string; + /** + * The authorization component used to guard accounts with escalated privilege. + * @default authorization + */ + authorizationComponentType?: string; + /** * The configuration for the authentication. */ diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts index a3d8c24..70f864c 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts @@ -1,6 +1,6 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import type { IAuthenticationRateActionConfig } from "@3sixty/api-auth-entity-storage-models"; +import type { IAuthenticationRateActionConfig } from "@3sixty/api-auth-models"; /** * Configuration for the entity storage authentication service. @@ -20,10 +20,16 @@ export interface IEntityStorageAuthenticationServiceConfig { /** * The minimum password length for new password validation. - * @default 8 + * @default 15 */ minPasswordLength?: number; + /** + * The maximum password length for new password validation. + * @default 128 + */ + maxPasswordLength?: number; + /** * Optional override for login failure rate limit. * @default { maxAttempts: 5, windowMinutes: 15 } diff --git a/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts b/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts index f8b8742..fd9b21c 100644 --- a/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts +++ b/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts @@ -10,7 +10,16 @@ import { type ITenantAdminComponent } from "@3sixty/api-models"; import { ContextIdHelper, ContextIdKeys, ContextIdStore, type IContextIds } from "@3sixty/context"; -import { BaseError, ComponentFactory, GeneralError, Is } from "@3sixty/core"; +import { + BaseError, + Coerce, + ComponentFactory, + Converter, + GeneralError, + Is, + LfuCache +} from "@3sixty/core"; +import { Blake2b } from "@3sixty/crypto"; import { EntityStorageConnectorFactory, type IEntityStorageConnector @@ -20,6 +29,7 @@ import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-model import { CookieHelper, HeaderTypes, HttpStatusCode } from "@3sixty/web"; import type { AuthenticationUser } from "../entities/authenticationUser.js"; import type { IAuthHeaderProcessorConstructorOptions } from "../models/IAuthHeaderProcessorConstructorOptions.js"; +import type { IAuthTokenContext } from "../models/IAuthTokenContext.js"; import { TokenHelper } from "../utils/tokenHelper.js"; /** @@ -37,6 +47,12 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { */ public static readonly CLASS_NAME: string = nameof(); + /** + * The default time to keep a verified token in the cache. + * @internal + */ + private static readonly _DEFAULT_TOKEN_CACHE_TTL_MS: number = 30000; + /** * The vault for the keys. * @internal @@ -85,6 +101,18 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { */ private _nodeOrganizationId?: string; + /** + * The cache of contexts resolved from verified tokens, undefined when caching is disabled. + * @internal + */ + private readonly _tokenCache?: LfuCache; + + /** + * How long a cached token context may live, counted from when it was resolved. + * @internal + */ + private readonly _tokenCacheTtlMs: number; + /** * Create a new instance of AuthHeaderProcessor. * @param options Options for the processor. @@ -101,6 +129,16 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing"; this._cookieName = options?.config?.cookieName ?? AuthHeaderProcessor.DEFAULT_COOKIE_NAME; this._includeErrorStack = options?.config?.includeErrorStack ?? false; + + this._tokenCacheTtlMs = + options?.config?.tokenCacheTtlMs ?? AuthHeaderProcessor._DEFAULT_TOKEN_CACHE_TTL_MS; + if (this._tokenCacheTtlMs > 0) { + this._tokenCache = new LfuCache({ + capacity: options?.config?.tokenCacheCapacity, + ttiMs: this._tokenCacheTtlMs, + mutexTimeoutMs: options?.config?.tokenCacheMutexTimeoutMs + }); + } } /** @@ -123,6 +161,14 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { this._nodeOrganizationId = contextIds[ContextIdKeys.Organization]; } + /** + * Stop the processor and release the token cache. + * @returns A promise that resolves when the processor has stopped. + */ + public async stop(): Promise { + this._tokenCache?.destroy(); + } + /** * Pre process the REST request for the specified route. * @param request The incoming request. @@ -150,68 +196,23 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { this._cookieName ); - let user: AuthenticationUser | undefined; - let tenantId: string | undefined; - let tenantOrganizationId: string | undefined; - let tenantPublicOrigin: string | undefined; - - await TokenHelper.verify( - this._vaultConnector, + const tokenContext = await this.resolveTokenContext( this._nodeId, - this._signingKeyName, tokenAndLocation?.token, - async ( - sub: string, - org: string, - tid: string | undefined, - passwordVersion: number | undefined - ) => { - const validParts = []; - - tenantId = tid; - - if (Is.stringValue(tenantId)) { - const tenant = await this._tenantAdminComponent?.get(tenantId); - if (tenant?.id === tenantId) { - validParts.push("tenant"); - tenantOrganizationId = tenant.organizationId; - tenantPublicOrigin = tenant.publicOrigin; - } - } - - // We use the tenant id from the token, if the user is not in that - // partition then the get will fail - const contextIdsForUserLookup = { - ...contextIds, - [ContextIdKeys.Tenant]: tid - }; - - // Wrap the user lookup in the request context so partitioned storage uses the correct tenant. - user = await ContextIdStore.run(contextIdsForUserLookup, async () => - this._userEntityStorage.get(sub, "identity") - ); - - if (user?.identity === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) { - validParts.push("user"); - } - if (user?.organization === org) { - validParts.push("organization"); - } - - return validParts; - } + contextIds ); - contextIds[ContextIdKeys.Tenant] = tenantId; + contextIds[ContextIdKeys.Tenant] = tokenContext.tenantId; // In a multi-tenant environment the tenant organization ID is authoritative, // in a single-tenant environment we fall back to the node organization ID. - contextIds[ContextIdKeys.Organization] = tenantOrganizationId ?? this._nodeOrganizationId; - contextIds[ContextIdKeys.User] = user?.identity; - contextIds[ContextIdKeys.UserOrganization] = user?.organization; + contextIds[ContextIdKeys.Organization] = + tokenContext.tenantOrganizationId ?? this._nodeOrganizationId; + contextIds[ContextIdKeys.User] = tokenContext.userIdentity; + contextIds[ContextIdKeys.UserOrganization] = tokenContext.userOrganization; // If the tenant has a custom public origin, we set it in the context for downstream processors to use. - if (Is.stringValue(tenantPublicOrigin)) { - contextIds[HttpContextIdKeys.PublicOrigin] = tenantPublicOrigin; + if (Is.stringValue(tokenContext.tenantPublicOrigin)) { + contextIds[HttpContextIdKeys.PublicOrigin] = tokenContext.tenantPublicOrigin; } processorState.authToken = tokenAndLocation?.token; @@ -279,4 +280,116 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { } } } + + /** + * Resolve the route independent context for a token, through the cache when one is enabled. + * @param nodeId The node identifier the token must have been issued by. + * @param token The token from the request, if there was one. + * @param contextIds The context IDs of the request. + * @returns The resolved context. + * @internal + */ + private async resolveTokenContext( + nodeId: string, + token: string | undefined, + contextIds: IContextIds + ): Promise { + if (Is.empty(this._tokenCache) || !Is.stringValue(token)) { + return this.verifyToken(nodeId, token, contextIds); + } + + // Use hash of token so we don't store the raw token in memory. + const cacheKey = Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes(token))); + + const tokenContext = await this._tokenCache.getOrSet( + cacheKey, + async () => this.verifyToken(nodeId, token, contextIds), + Date.now() + this._tokenCacheTtlMs + ); + + // A token can expire while its context sits in the cache. + if (Is.integer(tokenContext.expires) && tokenContext.expires <= Date.now()) { + this._tokenCache.delete(cacheKey); + return this.verifyToken(nodeId, token, contextIds); + } + + return tokenContext; + } + + /** + * Verify a token against the vault and resolve the tenant and user it refers to. + * @param nodeId The node identifier the token must have been issued by. + * @param token The token from the request, if there was one. + * @param contextIds The context IDs of the request. + * @returns The resolved context. + * @internal + */ + private async verifyToken( + nodeId: string, + token: string | undefined, + contextIds: IContextIds + ): Promise { + let user: AuthenticationUser | undefined; + let tenantId: string | undefined; + let tenantOrganizationId: string | undefined; + let tenantPublicOrigin: string | undefined; + + const { payload } = await TokenHelper.verify( + this._vaultConnector, + nodeId, + this._signingKeyName, + token, + async ( + sub: string, + org: string, + tid: string | undefined, + passwordVersion: number | undefined + ) => { + const validParts = []; + + tenantId = tid; + + if (Is.stringValue(tenantId)) { + const tenant = await this._tenantAdminComponent?.get(tenantId); + if (tenant?.id === tenantId) { + validParts.push("tenant"); + tenantOrganizationId = tenant.organizationId; + tenantPublicOrigin = tenant.publicOrigin; + } + } + + // We use the tenant id from the token, if the user is not in that + // partition then the get will fail + const contextIdsForUserLookup = { + ...contextIds, + [ContextIdKeys.Tenant]: tid + }; + + // Wrap the user lookup in the request context so partitioned storage uses the correct tenant. + user = await ContextIdStore.run(contextIdsForUserLookup, async () => + this._userEntityStorage.get(sub, "identity") + ); + + if (user?.identity === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) { + validParts.push("user"); + } + if (user?.organization === org) { + validParts.push("organization"); + } + + return validParts; + } + ); + + const expiresSeconds = Coerce.integer(payload.exp); + + return { + tenantId, + tenantOrganizationId, + tenantPublicOrigin, + userIdentity: user?.identity, + userOrganization: user?.organization, + expires: Is.integer(expiresSeconds) ? expiresSeconds * 1000 : undefined + }; + } } diff --git a/packages/api-auth-entity-storage-service/src/restEntryPoints.ts b/packages/api-auth-entity-storage-service/src/restEntryPoints.ts index 931612b..33a1f39 100644 --- a/packages/api-auth-entity-storage-service/src/restEntryPoints.ts +++ b/packages/api-auth-entity-storage-service/src/restEntryPoints.ts @@ -5,17 +5,13 @@ import { generateRestRoutesAuthenticationAdmin, tagsAuthenticationAdmin } from "./routes/entityStorageAuthenticationAdminRoutes.js"; -import { - generateRestRoutesAuthenticationAudit, - tagsAuthenticationAudit -} from "./routes/entityStorageAuthenticationAuditRoutes.js"; import { generateRestRoutesAuthentication, tagsAuthentication } from "./routes/entityStorageAuthenticationRoutes.js"; /** - * REST entry points for the authentication, authentication admin, and authentication audit services. + * REST entry points for the authentication and authentication admin services. */ export const restEntryPoints: IRestRouteEntryPoint[] = [ { @@ -29,11 +25,5 @@ export const restEntryPoints: IRestRouteEntryPoint[] = [ defaultBaseRoute: "authentication/admin", tags: tagsAuthenticationAdmin, generateRoutes: generateRestRoutesAuthenticationAdmin - }, - { - name: "authenticationAudit", - defaultBaseRoute: "authentication/audit", - tags: tagsAuthenticationAudit, - generateRoutes: generateRestRoutesAuthenticationAudit } ]; diff --git a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts index d736c05..fe0837a 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts @@ -18,6 +18,7 @@ import { type IHttpRequestContext, type INoContentResponse, type IRestRoute, + type IRouteAuthorization, type ITag, type IUnauthorizedResponse } from "@3sixty/api-models"; @@ -31,6 +32,18 @@ import { HttpStatusCode, type IHttpHeaders } from "@3sixty/web"; */ const ROUTES_SOURCE = "authenticationAdminRoutes"; +/** + * The default authorization for the routes, used to seed authorization rules. + */ +const DEFAULT_AUTHORIZATION_READER: IRouteAuthorization = { + permission: "user-admin:read", + role: "user-admin" +}; +const DEFAULT_AUTHORIZATION_WRITER: IRouteAuthorization = { + permission: "user-admin:write", + role: "user-admin", + inherits: [DEFAULT_AUTHORIZATION_READER.permission] +}; /** * The tag to associate with the routes. */ @@ -70,8 +83,7 @@ export function generateRestRoutesAuthenticationAdmin( email: "user@example.com", password: "MyPassword123!", userIdentity: "did:example:123456789abcdefghi", - organizationIdentity: "did:example:123456789abcdefghi", - scope: ["scope1", "scope2"] + organizationIdentity: "did:example:123456789abcdefghi" } } } @@ -84,7 +96,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const updateUserRoute: IRestRoute = { @@ -107,8 +120,7 @@ export function generateRestRoutesAuthenticationAdmin( }, body: { userIdentity: "did:example:123456789abcdefghi", - organizationIdentity: "did:example:123456789abcdefghi", - scope: ["scope1", "scope2"] + organizationIdentity: "did:example:123456789abcdefghi" } } } @@ -121,7 +133,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const updateUserPasswordRoute: IRestRoute = { @@ -156,7 +169,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const getUserRoute: IRestRoute = { @@ -192,8 +206,7 @@ export function generateRestRoutesAuthenticationAdmin( body: { email: "user@example.com", userIdentity: "did:example:123456789abcdefghi", - organizationIdentity: "did:example:123456789abcdefghi", - scope: ["scope1", "scope2"] + organizationIdentity: "did:example:123456789abcdefghi" } } } @@ -202,7 +215,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_READER }; const getByIdentityUserRoute: IRestRoute = @@ -239,8 +253,7 @@ export function generateRestRoutesAuthenticationAdmin( body: { email: "user@example.com", userIdentity: "did:example:123456789abcdefghi", - organizationIdentity: "did:example:123456789abcdefghi", - scope: ["scope1", "scope2"] + organizationIdentity: "did:example:123456789abcdefghi" } } } @@ -249,7 +262,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_READER }; const removeUserRoute: IRestRoute = { @@ -281,7 +295,8 @@ export function generateRestRoutesAuthenticationAdmin( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; return [ diff --git a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts index 61a6045..85eedde 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -5,12 +5,14 @@ import type { ILoginRequest, ILoginResponse, ILogoutRequest, + IPasswordPolicyResponse, IRefreshTokenRequest, IRefreshTokenResponse, IUpdatePasswordRequest } from "@3sixty/api-auth-entity-storage-models"; import type { IHttpRequestContext, + INoContentRequest, INoContentResponse, IRestRoute, IRestRouteResponseOptions, @@ -196,7 +198,39 @@ export function generateRestRoutesAuthentication( ] }; - return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute]; + const passwordPolicyRoute: IRestRoute = { + operationId: "authenticationPasswordPolicy", + summary: "Get the password policy", + tag: tagsAuthentication[0].name, + method: "GET", + path: `${baseRouteName}/policy`, + handler: async (httpRequestContext, request) => + authenticationPasswordPolicy(httpRequestContext, componentName, request), + requestType: { + type: nameof() + }, + responseType: [ + { + type: nameof(), + examples: [ + { + id: "passwordPolicyResponseExample", + description: "The response for the password policy request.", + response: { + body: { + minPasswordLength: 15, + maxPasswordLength: 128 + } + } + } + ] + } + ], + skipAuth: true, + skipTenant: true + }; + + return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute, passwordPolicyRoute]; } /** @@ -311,3 +345,23 @@ export async function authenticationUpdatePassword( statusCode: HttpStatusCode.noContent }; } + +/** + * Get the password policy. + * @param httpRequestContext The request context for the API. + * @param componentName The name of the component to use in the routes. + * @param request The request. + * @returns The response object with additional http response properties. + */ +export async function authenticationPasswordPolicy( + httpRequestContext: IHttpRequestContext, + componentName: string, + request: INoContentRequest +): Promise { + const component = ComponentFactory.get(componentName); + const result = await component.passwordPolicy(); + + return { + body: result + }; +} diff --git a/packages/api-auth-entity-storage-service/src/schema.ts b/packages/api-auth-entity-storage-service/src/schema.ts index 80b63d5..3fe63a2 100644 --- a/packages/api-auth-entity-storage-service/src/schema.ts +++ b/packages/api-auth-entity-storage-service/src/schema.ts @@ -2,9 +2,8 @@ // SPDX-License-Identifier: Apache-2.0. import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity"; import { nameof } from "@3sixty/nameof"; -import { AuthenticationAuditEntry } from "./entities/authenticationAuditEntry.js"; -import { AuthenticationRateEntry } from "./entities/authenticationRateEntry.js"; import { AuthenticationUser } from "./entities/authenticationUser.js"; +import { AuthenticationUserV0 } from "./entities/authenticationUserV0.js"; /** * Initialize the schema for the authentication service. @@ -13,10 +12,7 @@ export function initSchema(): void { EntitySchemaFactory.register(nameof(), () => EntitySchemaHelper.getSchema(AuthenticationUser) ); - EntitySchemaFactory.register(nameof(), () => - EntitySchemaHelper.getSchema(AuthenticationAuditEntry) - ); - EntitySchemaFactory.register(nameof(), () => - EntitySchemaHelper.getSchema(AuthenticationRateEntry) + EntitySchemaFactory.register(nameof(), () => + EntitySchemaHelper.getSchema(AuthenticationUserV0) ); } diff --git a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts index 2fc7c9b..c7d5a0a 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationAdminComponent, - IAuthenticationAuditComponent, IAuthenticationUser } from "@3sixty/api-auth-entity-storage-models"; -import { AuthAuditEvent } from "@3sixty/api-auth-entity-storage-models"; +import { type IAuthenticationAuditComponent, AuthAuditEvent } from "@3sixty/api-auth-models"; +import { ForbiddenError, HttpContextIdKeys } from "@3sixty/api-models"; +import type { IAuthorizationComponent } from "@3sixty/authorization-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; import { + BaseError, ComponentFactory, Converter, GeneralError, @@ -35,6 +37,16 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA */ public static readonly CLASS_NAME: string = nameof(); + /** + * The default role that grants escalated privilege. + */ + public static readonly DEFAULT_ESCALATED_PRIVILEGE_ROLE: string = "global-admin"; + + /** + * The default authorization model ID used to look up roles. + */ + public static readonly DEFAULT_AUTHORIZATION_MODEL_ID: string = "system"; + /** * The entity storage for users. * @internal @@ -42,17 +54,41 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA private readonly _userEntityStorage: IEntityStorageConnector; /** - * The audit service. + * The audit component. * @internal */ private readonly _authenticationAuditService?: IAuthenticationAuditComponent; + /** + * The authorization component used to guard accounts with escalated privilege. + * @internal + */ + private readonly _authorizationComponent?: IAuthorizationComponent; + + /** + * The role that grants escalated privilege. + * @internal + */ + private readonly _escalatedPrivilegeRole: string; + + /** + * The authorization model ID used to look up roles. + * @internal + */ + private readonly _authorizationModelId: string; + /** * The minimum password length. * @internal */ private readonly _minPasswordLength?: number; + /** + * The maximum password length. + * @internal + */ + private readonly _maxPasswordLength?: number; + /** * Create a new instance of EntityStorageAuthentication. * @param options The dependencies for the identity connector. @@ -66,7 +102,18 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA options?.authenticationAuditServiceType ?? "authentication-audit" ); + this._authorizationComponent = ComponentFactory.getIfExists( + options?.authorizationComponentType ?? "authorization" + ); + this._minPasswordLength = options?.config?.minPasswordLength; + this._maxPasswordLength = options?.config?.maxPasswordLength; + this._escalatedPrivilegeRole = + options?.config?.escalatedPrivilegeRole ?? + EntityStorageAuthenticationAdminService.DEFAULT_ESCALATED_PRIVILEGE_ROLE; + this._authorizationModelId = + options?.config?.authorizationModelId ?? + EntityStorageAuthenticationAdminService.DEFAULT_AUTHORIZATION_MODEL_ID; } /** @@ -108,15 +155,11 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA nameof(user.organizationIdentity), user.organizationIdentity ); - Guards.array( - EntityStorageAuthenticationAdminService.CLASS_NAME, - nameof(user.scope), - user.scope - ); try { PasswordValidator.validatePassword(user.password, { - minLength: this._minPasswordLength + minLength: this._minPasswordLength, + maxLength: this._maxPasswordLength }); const existingUser = await this._userEntityStorage.get(user.email); @@ -124,6 +167,9 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists"); } + // Roles belong to the identity, so a login mapped to an escalated identity inherits them. + await this.guardEscalatedPrivilegeTarget(user.userIdentity); + const saltBytes = RandomHelper.generate(16); const passwordBytes = Converter.utf8ToBytes(user.password); @@ -135,7 +181,6 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA password: hashedPassword, identity: user.userIdentity, organization: user.organizationIdentity, - scope: user.scope.map(s => s.trim().toLocaleLowerCase()).join(","), passwordVersion: 0 }; @@ -143,6 +188,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA const contextIds = await ContextIdStore.getContextIds(); const requestorTenantId = contextIds?.[ContextIdKeys.Tenant]; + const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant]; await this._authenticationAuditService?.create({ actorId: user.email, event: AuthAuditEvent.AccountCreated, @@ -150,10 +196,13 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA userIdentity: user.userIdentity, organizationIdentity: user.organizationIdentity, tenantId: requestorTenantId, - scope: user.scope + originalTenantId } }); } catch (error) { + if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) { + throw error; + } throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, "createUserFailed", @@ -194,13 +243,6 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA user.organizationIdentity ); } - if (!Is.empty(user.scope)) { - Guards.array( - EntityStorageAuthenticationAdminService.CLASS_NAME, - nameof(user.scope), - user.scope - ); - } try { const existingUser = await this._userEntityStorage.get(user.email); @@ -212,10 +254,12 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } + await this.guardEscalatedPrivilegeTarget(existingUser.identity); + if (Is.stringValue(user.userIdentity) && user.userIdentity !== existingUser.identity) { + await this.guardEscalatedPrivilegeTarget(user.userIdentity); + } + const updatedFields: string[] = []; - const updatedScope = Is.array(user.scope) - ? user.scope.map(s => s.trim().toLocaleLowerCase()).join(",") - : existingUser.scope; if (user.userIdentity !== undefined && user.userIdentity !== existingUser.identity) { updatedFields.push("userIdentity"); @@ -226,18 +270,15 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ) { updatedFields.push("organizationIdentity"); } - if (Is.array(user.scope) && updatedScope !== existingUser.scope) { - updatedFields.push("scope"); - } existingUser.identity = user.userIdentity ?? existingUser.identity; existingUser.organization = user.organizationIdentity ?? existingUser.organization; - existingUser.scope = Is.array(user.scope) ? updatedScope : existingUser.scope; await this._userEntityStorage.set(existingUser); const contextIds = await ContextIdStore.getContextIds(); const requestorTenantId = contextIds?.[ContextIdKeys.Tenant]; + const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant]; await this._authenticationAuditService?.create({ actorId: existingUser.email, event: AuthAuditEvent.AccountUpdated, @@ -246,10 +287,13 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA userIdentity: existingUser.identity, organizationIdentity: existingUser.organization, tenantId: requestorTenantId, - scope: existingUser.scope.split(",") + originalTenantId } }); } catch (error) { + if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) { + throw error; + } throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, "updateUserFailed", @@ -280,8 +324,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA return { email: user.email, userIdentity: user.identity, - organizationIdentity: user.organization, - scope: user.scope.split(",") + organizationIdentity: user.organization }; } catch (error) { throw new GeneralError( @@ -318,8 +361,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA return { email: user.email, userIdentity: user.identity, - organizationIdentity: user.organization, - scope: user.scope.split(",") + organizationIdentity: user.organization }; } catch (error) { throw new GeneralError( @@ -349,10 +391,13 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } + await this.guardEscalatedPrivilegeTarget(user.identity); + await this._userEntityStorage.remove(email); const contextIds = await ContextIdStore.getContextIds(); const requestorTenantId = contextIds?.[ContextIdKeys.Tenant]; + const originalTenantId = contextIds?.[HttpContextIdKeys.OriginalTenant]; await this._authenticationAuditService?.create({ actorId: email, event: AuthAuditEvent.AccountDeleted, @@ -360,10 +405,13 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA userIdentity: user.identity, organizationIdentity: user.organization, tenantId: requestorTenantId, - scope: user.scope.split(",") + originalTenantId } }); } catch (error) { + if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) { + throw error; + } throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, "removeUserFailed", @@ -402,15 +450,23 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } + await this.guardEscalatedPrivilegeTarget(user.identity); + await PasswordHelper.updatePassword( this._userEntityStorage, this._authenticationAuditService, user, newPassword, currentPassword, - this._minPasswordLength + { + minPasswordLength: this._minPasswordLength, + maxPasswordLength: this._maxPasswordLength + } ); } catch (error) { + if (BaseError.isErrorName(error, ForbiddenError.CLASS_NAME)) { + throw error; + } throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, "updatePasswordFailed", @@ -419,4 +475,43 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } } + + /** + * Ensure the caller holds escalated privilege when the target identity holds it. + * @param targetIdentity The identity of the account being modified. + * @throws ForbiddenError if the target holds escalated privilege and the caller does not. + * @internal + */ + private async guardEscalatedPrivilegeTarget(targetIdentity: string): Promise { + if (Is.empty(this._authorizationComponent)) { + return; + } + + const targetIsEscalated = await this._authorizationComponent.hasRoleForSubject( + this._authorizationModelId, + targetIdentity, + this._escalatedPrivilegeRole + ); + if (!targetIsEscalated) { + return; + } + + const contextIds = await ContextIdStore.getContextIds(); + const callerIdentity = contextIds?.[ContextIdKeys.User]; + const callerIsEscalated = + Is.stringValue(callerIdentity) && + (await this._authorizationComponent.hasRoleForSubject( + this._authorizationModelId, + callerIdentity, + this._escalatedPrivilegeRole + )); + + if (!callerIsEscalated) { + throw new ForbiddenError( + EntityStorageAuthenticationAdminService.CLASS_NAME, + "insufficientRoleForEscalatedTarget", + { roleName: this._escalatedPrivilegeRole } + ); + } + } } diff --git a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts index a0cf729..f4d5862 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -1,12 +1,15 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. +import type { + IAuthenticationComponent, + IPasswordOptions +} from "@3sixty/api-auth-entity-storage-models"; import type { IAuthenticationRateActionConfig, IAuthenticationRateComponent, - IAuthenticationAuditComponent, - IAuthenticationComponent -} from "@3sixty/api-auth-entity-storage-models"; -import { AuthAuditEvent } from "@3sixty/api-auth-entity-storage-models"; + IAuthenticationAuditComponent +} from "@3sixty/api-auth-models"; +import { AuthAuditEvent } from "@3sixty/api-auth-models"; import type { ITenantAdminComponent } from "@3sixty/api-models"; import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context"; import { @@ -124,6 +127,12 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon */ private readonly _minPasswordLength?: number; + /** + * The maximum password length for validation. + * @internal + */ + private readonly _maxPasswordLength?: number; + /** * Rate limit configuration for login failures. * @internal @@ -181,6 +190,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon this._defaultTtlMinutes = options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES; this._minPasswordLength = options?.config?.minPasswordLength; + this._maxPasswordLength = options?.config?.maxPasswordLength; this._loginRateLimit = { maxAttempts: options?.config?.loginRateLimit?.maxAttempts ?? @@ -302,7 +312,6 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon user.organization, loginTenantId, this._defaultTtlMinutes, - user.scope, user.passwordVersion ?? 0 ); loginUser = user; @@ -328,8 +337,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon data: { userIdentity: loginUser.identity, organizationIdentity: loginUser.organization, - tenantId: loginTenantId, - scope: loginUser.scope.split(",") + tenantId: loginTenantId } }); @@ -367,8 +375,8 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet"); } - let refreshPasswordVersion: number | undefined; let tenantId: string | undefined; + let refreshUser: AuthenticationUser | undefined; // If the verify fails on the current token then it will throw an exception. const headerAndPayload = await TokenHelper.verify( @@ -401,8 +409,9 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon this._userEntityStorage.get(sub, "identity") ); - refreshPasswordVersion = user?.passwordVersion; - if (user?.identity === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) { + refreshUser = user; + + if (user?.identity === sub && (passwordVersion ?? 0) === (user?.passwordVersion ?? 0)) { validParts.push("user"); } if (user?.organization === org) { @@ -416,7 +425,6 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon await this._authenticationRateService.check("token-refresh", refreshSub); const payloadOrg = Coerce.string(headerAndPayload.payload.org); - const payloadScope = Coerce.string(headerAndPayload.payload?.scope); const refreshTokenAndExpiry = await TokenHelper.createToken( this._vaultConnector, @@ -426,8 +434,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon payloadOrg, tenantId, this._defaultTtlMinutes, - payloadScope, - refreshPasswordVersion ?? 0 + refreshUser?.passwordVersion ?? 0 ); await this._authenticationAuditService?.create({ @@ -436,8 +443,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon data: { organizationIdentity: payloadOrg, tenantId, - scope: payloadScope?.split(",").filter(scope => scope.length > 0), - version: refreshPasswordVersion ?? 0 + version: refreshUser?.passwordVersion ?? 0 } }); @@ -472,9 +478,23 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon user, newPassword, currentPassword, - this._minPasswordLength + { + minPasswordLength: this._minPasswordLength, + maxPasswordLength: this._maxPasswordLength + } ); await this._authenticationRateService.clear("password-change", userIdentity); } + + /** + * Get the password policy used for password validation. + * @returns The password policy options. + */ + public async passwordPolicy(): Promise { + return { + minPasswordLength: this._minPasswordLength, + maxPasswordLength: this._maxPasswordLength + }; + } } diff --git a/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts b/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts index 04ead46..7ac52e0 100644 --- a/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts +++ b/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts @@ -1,7 +1,8 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-entity-storage-models"; -import { AuthAuditEvent } from "@3sixty/api-auth-entity-storage-models"; +import type { IPasswordOptions } from "@3sixty/api-auth-entity-storage-models"; +import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-models"; +import { AuthAuditEvent } from "@3sixty/api-auth-models"; import { Converter, GeneralError, Is, RandomHelper } from "@3sixty/core"; import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto"; import type { IEntityStorageConnector } from "@3sixty/entity-storage-models"; @@ -25,7 +26,9 @@ export class PasswordHelper { * @param user The user whose password is being updated. * @param newPassword The new password to set. * @param currentPassword The current password to verify against, if supplied. - * @param minPasswordLength Optional minimum password length for validation. + * @param options Optional password length validation options. + * @param options.minPasswordLength Optional minimum password length for validation. + * @param options.maxPasswordLength Optional maximum password length for validation. * @returns A promise that resolves when the new password has been stored and the audit entry recorded. */ public static async updatePassword( @@ -34,10 +37,11 @@ export class PasswordHelper { user: AuthenticationUser, newPassword: string, currentPassword?: string, - minPasswordLength?: number + options?: IPasswordOptions ): Promise { PasswordValidator.validatePassword(newPassword, { - minLength: minPasswordLength + minLength: options?.minPasswordLength, + maxLength: options?.maxPasswordLength }); if (Is.stringValue(currentPassword)) { @@ -59,7 +63,6 @@ export class PasswordHelper { password: hashedPassword, identity: user.identity, organization: user.organization, - scope: user.scope, passwordVersion: (user.passwordVersion ?? 0) + 1 }; diff --git a/packages/api-auth-entity-storage-service/src/utils/tokenHelper.ts b/packages/api-auth-entity-storage-service/src/utils/tokenHelper.ts index 36fa152..b65f004 100644 --- a/packages/api-auth-entity-storage-service/src/utils/tokenHelper.ts +++ b/packages/api-auth-entity-storage-service/src/utils/tokenHelper.ts @@ -31,7 +31,6 @@ export class TokenHelper { * @param organizationIdentity The organization for the token. * @param tenantId The tenant id for the token. * @param ttlMinutes The time to live for the token in minutes. - * @param scope The scopes for the token. * @param passwordVersion The user's current password version counter, embedded in the token so that a password change invalidates existing tokens. * @returns The new token and its expiry date. */ @@ -43,7 +42,6 @@ export class TokenHelper { organizationIdentity: string | undefined, tenantId: string | undefined, ttlMinutes: number, - scope?: string, passwordVersion?: number ): Promise<{ token: string; @@ -61,7 +59,6 @@ export class TokenHelper { org: organizationIdentity, tid: tenantId, exp: nowSeconds + ttlSeconds, - scope, pver: passwordVersion }, async (header, payload) => diff --git a/packages/api-auth-entity-storage-service/tests/helpers/tokenHelper.spec.ts b/packages/api-auth-entity-storage-service/tests/helpers/tokenHelper.spec.ts index fe4e6be..930b24f 100644 --- a/packages/api-auth-entity-storage-service/tests/helpers/tokenHelper.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/helpers/tokenHelper.spec.ts @@ -183,89 +183,6 @@ describe("TokenHelper", () => { }); }); - describe("verify with scopes", () => { - const mockVaultConnector: IVaultConnector = { - get: vi.fn(), - set: vi.fn(), - remove: vi.fn() - } as unknown as IVaultConnector; - - const nodeId = "test-node"; - const signingKeyName = "test-key"; - - it("should verify token with matching required scopes", async () => { - const payload = { - iss: nodeId, - sub: "user123", - org: "org123", - exp: Math.trunc(Date.now() / 1000) + 3600, - scope: "read,write,admin" - }; - - const token = "mock.jwt.token"; - vi.spyOn(Jwt, "verifyWithVerifier").mockResolvedValue({ - header: { alg: "EdDSA", kid: signingKeyName }, - payload - }); - - const result = await TokenHelper.verify(mockVaultConnector, nodeId, signingKeyName, token); - - expect(result.payload).toEqual(payload); - }); - - it("should verify token when no scopes are required", async () => { - const payload = { - iss: nodeId, - sub: "user123", - org: "org123", - exp: Math.trunc(Date.now() / 1000) + 3600, - scope: "read,write" - }; - - const token = "mock.jwt.token"; - vi.spyOn(Jwt, "verifyWithVerifier").mockResolvedValue({ - header: { alg: "EdDSA", kid: signingKeyName }, - payload - }); - - const result = await TokenHelper.verify(mockVaultConnector, nodeId, signingKeyName, token); - - expect(result.payload).toEqual(payload); - }); - - it("should verify token when required scopes is empty array", async () => { - const payload = { - iss: nodeId, - sub: "user123", - org: "org123", - exp: Math.trunc(Date.now() / 1000) + 3600, - scope: "read" - }; - - const token = "mock.jwt.token"; - vi.spyOn(Jwt, "verifyWithVerifier").mockResolvedValue({ - header: { alg: "EdDSA", kid: signingKeyName }, - payload - }); - - const result = await TokenHelper.verify(mockVaultConnector, nodeId, signingKeyName, token); - - expect(result.payload).toEqual(payload); - }); - - it("should throw UnauthorizedError for missing token", async () => { - await expect( - TokenHelper.verify(mockVaultConnector, nodeId, signingKeyName, undefined) - ).rejects.toThrow(UnauthorizedError); - }); - - it("should throw UnauthorizedError for empty token", async () => { - await expect( - TokenHelper.verify(mockVaultConnector, nodeId, signingKeyName, "") - ).rejects.toThrow(UnauthorizedError); - }); - }); - describe("verify token validation", () => { const mockVaultConnector: IVaultConnector = { get: vi.fn(), @@ -471,8 +388,7 @@ describe("TokenHelper", () => { sub: "user123", org: "org456", exp: Math.trunc(Date.now() / 1000) + 3600, - customClaim: "customValue", - roles: ["admin", "editor"] + customClaim: "customValue" }; const token = "custom-claims.jwt.token"; diff --git a/packages/api-auth-entity-storage-service/tests/processors/authHeaderProcessor.spec.ts b/packages/api-auth-entity-storage-service/tests/processors/authHeaderProcessor.spec.ts index 301f02b..07dd70e 100644 --- a/packages/api-auth-entity-storage-service/tests/processors/authHeaderProcessor.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/processors/authHeaderProcessor.spec.ts @@ -2,7 +2,14 @@ // SPDX-License-Identifier: Apache-2.0. import { HttpErrorHelper, type IHttpResponse } from "@3sixty/api-models"; import { ContextIdKeys, ContextIdStore, type IContextIds } from "@3sixty/context"; -import { ComponentFactory, type IError, UnauthorizedError } from "@3sixty/core"; +import { + ComponentFactory, + Converter, + type IError, + LfuCache, + UnauthorizedError +} from "@3sixty/core"; +import { Blake2b } from "@3sixty/crypto"; import { EntityStorageConnectorFactory, type IEntityStorageConnector @@ -57,7 +64,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -111,7 +117,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -210,7 +215,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 1 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -257,8 +261,7 @@ describe("AuthHeaderProcessor", () => { identity: "did:user:123", organization: "did:org:456", password: "hashed", - salt: "salt", - scope: "user-admin" + salt: "salt" }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ token: "jwt", @@ -299,7 +302,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -354,7 +356,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -399,7 +400,6 @@ describe("AuthHeaderProcessor", () => { organization: "did:org:456", password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -452,7 +452,6 @@ describe("AuthHeaderProcessor", () => { organization: USER_ORG, password: "hashed", salt: "salt", - scope: "user-admin", passwordVersion: 0 }); vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ @@ -615,4 +614,240 @@ describe("AuthHeaderProcessor", () => { expect(response.headers).toBeUndefined(); }); }); + + describe("token cache", () => { + const USER: AuthenticationUser = { + email: "user@example.com", + identity: "did:user:123", + organization: "did:org:456", + password: "hashed", + salt: "salt", + passwordVersion: 0 + }; + + /** + * Stand in for a real verification, resolving the token through the supplied verifyUser + * callback so the tenant and user lookups behind it actually run. + * @param expSeconds The expiry claim to report for the token, if any. + */ + function mockVerify(expSeconds?: number): void { + vi.spyOn(TokenHelper, "verify").mockImplementation( + async (vault, nodeId, key, token, verifyUser) => { + await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + return { + header: { alg: "EdDSA" }, + payload: { sub: "did:user:123", org: "did:org:456", exp: expSeconds } + }; + } + ); + } + + /** + * Drive one request through the processor. + * @param authProcessor The processor under test. + * @returns The response and the context ids the processor populated. + */ + async function runRequest( + authProcessor: AuthHeaderProcessor + ): Promise<{ response: IHttpResponse; contextIds: IContextIds }> { + const response: IHttpResponse = {}; + const contextIds: IContextIds = {}; + await authProcessor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); + return { response, contextIds }; + } + + afterEach(() => { + vi.useRealTimers(); + }); + + beforeEach(() => { + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1", + [ContextIdKeys.Organization]: "did:org:456" + }); + vi.mocked(mockUserEntityStorage.get).mockResolvedValue(USER); + vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ + token: "jwt", + location: "authorization" + }); + }); + + it("verifies and looks the user up only once for a repeated token", async () => { + mockVerify(); + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + + for (let i = 0; i < 3; i++) { + const { response, contextIds } = await runRequest(authProcessor); + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); + } + + expect(TokenHelper.verify).toHaveBeenCalledTimes(1); + expect(mockUserEntityStorage.get).toHaveBeenCalledTimes(1); + + await authProcessor.stop(); + }); + + it("collapses concurrent requests with the same token into one verification", async () => { + let inFlight = 0; + let maxInFlight = 0; + vi.spyOn(TokenHelper, "verify").mockImplementation( + async (vault, nodeId, key, token, verifyUser) => { + inFlight++; + maxInFlight = Math.max(maxInFlight, inFlight); + await new Promise(resolve => setTimeout(resolve, 20)); + await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + inFlight--; + return { + header: { alg: "EdDSA" }, + payload: { sub: "did:user:123", org: "did:org:456" } + }; + } + ); + + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + + const responses = await Promise.all([0, 1, 2, 3].map(async () => runRequest(authProcessor))); + + for (const { response } of responses) { + expect(response.statusCode).toBeUndefined(); + } + expect(maxInFlight).toBe(1); + expect(TokenHelper.verify).toHaveBeenCalledTimes(1); + expect(mockUserEntityStorage.get).toHaveBeenCalledTimes(1); + + await authProcessor.stop(); + }); + + it("keys the cache by a hash of the token, never the token itself", async () => { + mockVerify(); + const getOrSet = vi.spyOn(LfuCache.prototype, "getOrSet"); + + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + + const { response } = await runRequest(authProcessor); + expect(response.statusCode).toBeUndefined(); + + const cacheKey = getOrSet.mock.calls[0][0]; + expect(cacheKey).not.toBe("jwt"); + expect(cacheKey).toBe(Converter.bytesToHex(Blake2b.sum256(Converter.utf8ToBytes("jwt")))); + + await authProcessor.stop(); + }); + + it("never holds a token beyond its own expiry", async () => { + // Only the clock is faked; the cache and its mutex rely on real timers. + vi.useFakeTimers({ toFake: ["Date"] }); + const start = Date.now(); + mockVerify(Math.trunc(start / 1000) + 60); + + // The cache lifetime is far longer than the token, so only the expiry claim can be + // what ends the entry. + const authProcessor = new AuthHeaderProcessor({ config: { tokenCacheTtlMs: 600000 } }); + await authProcessor.start(); + + await runRequest(authProcessor); + expect(TokenHelper.verify).toHaveBeenCalledTimes(1); + + // Past the expiry claim the entry is dropped, so the token is verified afresh and the + // rejection comes from the normal verification path. + vi.setSystemTime(start + 61000); + + await runRequest(authProcessor); + expect(TokenHelper.verify).toHaveBeenCalledTimes(2); + + await authProcessor.stop(); + }); + + it("expires a cached token a fixed time after it was verified, however busy the caller", async () => { + // Only the clock is faked; the cache and its mutex rely on real timers. + vi.useFakeTimers({ toFake: ["Date"] }); + const start = Date.now(); + mockVerify(); + const authProcessor = new AuthHeaderProcessor({ config: { tokenCacheTtlMs: 60000 } }); + await authProcessor.start(); + + await runRequest(authProcessor); + + // Steady traffic inside the lifetime keeps hitting the same entry. + vi.setSystemTime(start + 30000); + await runRequest(authProcessor); + expect(TokenHelper.verify).toHaveBeenCalledTimes(1); + + // Those hits must not extend it; once the lifetime is up the token is verified afresh. + vi.setSystemTime(start + 70000); + await runRequest(authProcessor); + expect(TokenHelper.verify).toHaveBeenCalledTimes(2); + + await authProcessor.stop(); + }); + + it("verifies every request when caching is disabled", async () => { + mockVerify(); + const authProcessor = new AuthHeaderProcessor({ config: { tokenCacheTtlMs: 0 } }); + await authProcessor.start(); + + for (let i = 0; i < 3; i++) { + await runRequest(authProcessor); + } + + expect(TokenHelper.verify).toHaveBeenCalledTimes(3); + expect(mockUserEntityStorage.get).toHaveBeenCalledTimes(3); + + await authProcessor.stop(); + }); + + it("keeps distinct tokens apart", async () => { + mockVerify(); + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + + await runRequest(authProcessor); + + vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ + token: "other-jwt", + location: "authorization" + }); + await runRequest(authProcessor); + + expect(TokenHelper.verify).toHaveBeenCalledTimes(2); + + await authProcessor.stop(); + }); + + it("does not cache a failed verification", async () => { + vi.spyOn(TokenHelper, "verify").mockRejectedValue( + new UnauthorizedError("tokenHelper", "invalidSignature") + ); + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + + const first = await runRequest(authProcessor); + const second = await runRequest(authProcessor); + + expect(first.response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(second.response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(TokenHelper.verify).toHaveBeenCalledTimes(2); + + await authProcessor.stop(); + }); + + it("releases the cache on stop", async () => { + mockVerify(); + const authProcessor = new AuthHeaderProcessor(); + await authProcessor.start(); + await runRequest(authProcessor); + + await authProcessor.stop(); + + // A destroyed cache holds nothing, so the next request verifies again. + await runRequest(authProcessor); + + expect(TokenHelper.verify).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAdminService.spec.ts b/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAdminService.spec.ts index b98ae24..3ac283c 100644 --- a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAdminService.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAdminService.spec.ts @@ -1,6 +1,8 @@ // Copyright 2026 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-entity-storage-models"; +import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-models"; +import { ForbiddenError } from "@3sixty/api-models"; +import type { IAuthorizationComponent } from "@3sixty/authorization-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; import { ComponentFactory, GeneralError, RandomHelper } from "@3sixty/core"; import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto"; @@ -58,50 +60,13 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(service.className()).toBe(EntityStorageAuthenticationAdminService.CLASS_NAME); }); - it("should create a new user with normalised scopes", async () => { - vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); - vi.spyOn(RandomHelper, "generate").mockReturnValue(new Uint8Array([1, 2, 3, 4])); - vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("hashed-password"); - - await service.create({ - email: "user@example.com", - password: "correct-horse-battery", - userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: [" Read ", "WRITE"] - }); - - expect(PasswordValidator.validatePassword).toHaveBeenCalledWith("correct-horse-battery", { - minLength: 10 - }); - expect(await userEntityStorage.get("user@example.com")).toEqual({ - email: "user@example.com", - salt: "AQIDBA==", - password: "hashed-password", - identity: "did:user:123", - organization: "did:org:456", - scope: "read,write", - passwordVersion: 0 - }); - expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ - actorId: "user@example.com", - event: "account-created", - data: { - userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: [" Read ", "WRITE"] - } - }); - }); - it("should wrap create failures when the user already exists", async () => { await userEntityStorage.set({ email: "user@example.com", password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); @@ -110,8 +75,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "correct-horse-battery", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read"] + organizationIdentity: "did:org:456" }) ).rejects.toThrow(GeneralError); expect(await userEntityStorage.get("user@example.com")).toMatchObject({ @@ -129,49 +93,12 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "short", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read"] + organizationIdentity: "did:org:456" }) ).rejects.toThrow(GeneralError); expect(await userEntityStorage.get("user@example.com")).toBeUndefined(); }); - it("should update an existing user and normalise scopes", async () => { - await userEntityStorage.set({ - email: "user@example.com", - password: "stored-password", - salt: "AQIDBA==", - identity: "did:user:123", - organization: "did:org:456", - scope: "read" - }); - - await service.update({ - email: "user@example.com", - organizationIdentity: "did:org:999", - scope: [" Admin ", "WRITE"] - }); - - expect(await userEntityStorage.get("user@example.com")).toEqual({ - email: "user@example.com", - password: "stored-password", - salt: "AQIDBA==", - identity: "did:user:123", - organization: "did:org:999", - scope: "admin,write" - }); - expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ - actorId: "user@example.com", - event: "account-updated", - data: { - updatedFields: ["organizationIdentity", "scope"], - userIdentity: "did:user:123", - organizationIdentity: "did:org:999", - scope: ["admin", "write"] - } - }); - }); - it("should wrap update failures when the user is missing", async () => { await expect( service.update({ @@ -188,8 +115,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); const result = await service.get("user@example.com"); @@ -197,8 +123,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read", "write"] + organizationIdentity: "did:org:456" }); }); @@ -208,8 +133,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); const result = await service.getByIdentity("did:user:123"); @@ -217,8 +141,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read", "write"] + organizationIdentity: "did:org:456" }); }); @@ -236,8 +159,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); await service.remove("user@example.com"); @@ -248,8 +170,7 @@ describe("EntityStorageAuthenticationAdminService", () => { event: "account-deleted", data: { userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read"] + organizationIdentity: "did:org:456" } }); }); @@ -267,8 +188,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(true); @@ -285,7 +205,6 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "new-password-hash", identity: "did:user:123", organization: "did:org:456", - scope: "read,write", passwordVersion: 1 }); expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ @@ -304,8 +223,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); const comparePasswordHashesSpy = vi.spyOn(PasswordValidator, "comparePasswordHashes"); @@ -321,7 +239,6 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "new-password-hash", identity: "did:user:123", organization: "did:org:456", - scope: "read,write", passwordVersion: 1 }); }); @@ -332,8 +249,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("current-password-hash"); @@ -396,8 +312,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "correct-horse-battery", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read"] + organizationIdentity: "did:org:456" }); expect(await userEntityStorage.get("user@example.com")).toEqual({ @@ -406,7 +321,6 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "hashed-password", identity: "did:user:123", organization: "did:org:456", - scope: "read", passwordVersion: 0 }); }); @@ -419,8 +333,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); const result = await service.get("user@example.com"); @@ -428,8 +341,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read", "write"] + organizationIdentity: "did:org:456" }); }); }); @@ -441,8 +353,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); const result = await service.getByIdentity("did:user:123"); @@ -450,8 +361,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", userIdentity: "did:user:123", - organizationIdentity: "did:org:456", - scope: ["read", "write"] + organizationIdentity: "did:org:456" }); }); }); @@ -463,19 +373,16 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); await service.update({ email: "user@example.com", - organizationIdentity: "did:org:999", - scope: ["admin"] + organizationIdentity: "did:org:999" }); expect(await userEntityStorage.get("user@example.com")).toMatchObject({ - organization: "did:org:999", - scope: "admin" + organization: "did:org:999" }); }); }); @@ -487,8 +394,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); await service.remove("user@example.com"); @@ -508,8 +414,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read,write" + organization: "did:org:456" }); await service.updatePassword("user@example.com", "better-password-value"); @@ -532,8 +437,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "correct-horse-battery", userIdentity: "did:user:tenant-a", - organizationIdentity: "did:org:456", - scope: ["read"] + organizationIdentity: "did:org:456" }); }); @@ -542,19 +446,18 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "correct-horse-battery", userIdentity: "did:user:tenant-b", - organizationIdentity: "did:org:456", - scope: ["write"] + organizationIdentity: "did:org:456" }); }); await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_A }, async () => { const userA = await service.get("user@example.com"); - expect(userA).toMatchObject({ userIdentity: "did:user:tenant-a", scope: ["read"] }); + expect(userA).toMatchObject({ userIdentity: "did:user:tenant-a" }); }); await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_B }, async () => { const userB = await service.get("user@example.com"); - expect(userB).toMatchObject({ userIdentity: "did:user:tenant-b", scope: ["write"] }); + expect(userB).toMatchObject({ userIdentity: "did:user:tenant-b" }); }); }); @@ -565,8 +468,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); }); @@ -582,8 +484,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); }); @@ -599,8 +500,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); }); @@ -624,8 +524,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); }); @@ -647,8 +546,7 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "stored-password", salt: "AQIDBA==", identity: "did:user:123", - organization: "did:org:456", - scope: "read" + organization: "did:org:456" }); }); @@ -665,4 +563,180 @@ describe("EntityStorageAuthenticationAdminService", () => { }); }); }); + + describe("escalated privilege", () => { + const ESCALATED_IDENTITY = "did:user:admin"; + const PLAIN_IDENTITY = "did:user:plain"; + const CALLER_IDENTITY = "did:user:caller"; + + let mockAuthorizationComponent: IAuthorizationComponent; + let escalatedIdentities: string[]; + let guardedService: EntityStorageAuthenticationAdminService; + + /** + * Set the identity of the caller in the request context. + * @param identity The caller identity, if any. + */ + function setCaller(identity?: string): void { + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.User]: identity + }); + } + + beforeEach(async () => { + escalatedIdentities = [ESCALATED_IDENTITY]; + + mockAuthorizationComponent = { + hasRoleForSubject: vi.fn( + async (modelId: string, subject: string, role: string) => + role === "global-admin" && escalatedIdentities.includes(subject) + ) + } as unknown as IAuthorizationComponent; + + vi.spyOn(ComponentFactory, "getIfExists").mockImplementation(componentName => { + if (componentName === "authentication-audit") { + return mockAuthenticationAuditService; + } + if (componentName === "authorization") { + return mockAuthorizationComponent; + } + return undefined; + }); + vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); + + guardedService = new EntityStorageAuthenticationAdminService({ + config: { minPasswordLength: 10 } + }); + + await userEntityStorage.set({ + email: "admin@example.com", + password: "stored-password", + salt: "AQIDBA==", + identity: ESCALATED_IDENTITY, + organization: "did:org:456" + }); + await userEntityStorage.set({ + email: "plain@example.com", + password: "stored-password", + salt: "AQIDBA==", + identity: PLAIN_IDENTITY, + organization: "did:org:456" + }); + }); + + it("should forbid removing an escalated account when the caller is not escalated", async () => { + setCaller(CALLER_IDENTITY); + + await expect(guardedService.remove("admin@example.com")).rejects.toMatchObject({ + name: ForbiddenError.CLASS_NAME, + message: "entityStorageAuthenticationAdminService.insufficientRoleForEscalatedTarget" + }); + expect(await userEntityStorage.get("admin@example.com")).toBeDefined(); + }); + + it("should allow removing an escalated account when the caller is escalated", async () => { + escalatedIdentities.push(CALLER_IDENTITY); + setCaller(CALLER_IDENTITY); + + await guardedService.remove("admin@example.com"); + + expect(await userEntityStorage.get("admin@example.com")).toBeUndefined(); + }); + + it("should allow removing a plain account without checking the caller", async () => { + setCaller(CALLER_IDENTITY); + + await guardedService.remove("plain@example.com"); + + expect(await userEntityStorage.get("plain@example.com")).toBeUndefined(); + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledTimes(1); + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledWith( + "system", + PLAIN_IDENTITY, + "global-admin" + ); + }); + + it("should forbid modifying an escalated account when there is no caller", async () => { + setCaller(undefined); + + await expect(guardedService.remove("admin@example.com")).rejects.toMatchObject({ + name: ForbiddenError.CLASS_NAME + }); + }); + + it("should forbid resetting the password of an escalated account when the caller is not escalated", async () => { + setCaller(CALLER_IDENTITY); + + await expect( + guardedService.updatePassword("admin@example.com", "new-password-123") + ).rejects.toMatchObject({ name: ForbiddenError.CLASS_NAME }); + expect(await userEntityStorage.get("admin@example.com")).toMatchObject({ + password: "stored-password" + }); + }); + + it("should forbid updating an escalated account when the caller is not escalated", async () => { + setCaller(CALLER_IDENTITY); + + await expect( + guardedService.update({ email: "admin@example.com", organizationIdentity: "did:org:789" }) + ).rejects.toMatchObject({ name: ForbiddenError.CLASS_NAME }); + expect(await userEntityStorage.get("admin@example.com")).toMatchObject({ + organization: "did:org:456" + }); + }); + + it("should forbid pointing an account at an escalated identity when the caller is not escalated", async () => { + setCaller(CALLER_IDENTITY); + + await expect( + guardedService.update({ email: "plain@example.com", userIdentity: ESCALATED_IDENTITY }) + ).rejects.toMatchObject({ name: ForbiddenError.CLASS_NAME }); + expect(await userEntityStorage.get("plain@example.com")).toMatchObject({ + identity: PLAIN_IDENTITY + }); + }); + + it("should forbid creating an account for an escalated identity when the caller is not escalated", async () => { + setCaller(CALLER_IDENTITY); + + await expect( + guardedService.create({ + email: "new@example.com", + password: "correct-horse-battery", + userIdentity: ESCALATED_IDENTITY, + organizationIdentity: "did:org:456" + }) + ).rejects.toMatchObject({ name: ForbiddenError.CLASS_NAME }); + expect(await userEntityStorage.get("new@example.com")).toBeUndefined(); + }); + + it("should use the configured role and authorization model", async () => { + setCaller(CALLER_IDENTITY); + const customService = new EntityStorageAuthenticationAdminService({ + config: { escalatedPrivilegeRole: "super-admin", authorizationModelId: "custom-model" } + }); + + await customService.remove("admin@example.com"); + + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledWith( + "custom-model", + ESCALATED_IDENTITY, + "super-admin" + ); + expect(await userEntityStorage.get("admin@example.com")).toBeUndefined(); + }); + + it("should skip the guard when no authorization component is registered", async () => { + vi.spyOn(ComponentFactory, "getIfExists").mockReturnValue(undefined); + setCaller(CALLER_IDENTITY); + const unguardedService = new EntityStorageAuthenticationAdminService(); + + await unguardedService.remove("admin@example.com"); + + expect(await userEntityStorage.get("admin@example.com")).toBeUndefined(); + expect(mockAuthorizationComponent.hasRoleForSubject).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationService.spec.ts b/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationService.spec.ts index 9f51b4d..9f81cf0 100644 --- a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationService.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationService.spec.ts @@ -3,7 +3,7 @@ import type { IAuthenticationAuditComponent, IAuthenticationRateComponent -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { TooManyRequestsError } from "@3sixty/api-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; import { ComponentFactory, NotFoundError, UnauthorizedError } from "@3sixty/core"; @@ -98,8 +98,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(true); @@ -121,7 +120,6 @@ describe("EntityStorageAuthenticationService", () => { "did:org:456", "tenant-1", 60, - "read,write", 0 ); expect(mockAuthenticationRateService.clear).toHaveBeenCalledWith("login", "user@example.com"); @@ -131,8 +129,7 @@ describe("EntityStorageAuthenticationService", () => { data: { userIdentity: "did:user:123", organizationIdentity: "did:org:456", - tenantId: "tenant-1", - scope: ["read", "write"] + tenantId: "tenant-1" } }); }); @@ -146,8 +143,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(false); @@ -187,8 +183,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(true); @@ -268,8 +263,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(false); @@ -304,8 +298,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { @@ -315,8 +308,7 @@ describe("EntityStorageAuthenticationService", () => { header: { alg: "EdDSA" }, payload: { sub: "did:user:123", - org: "did:org:456", - scope: "read,write" + org: "did:org:456" } }; } @@ -339,7 +331,6 @@ describe("EntityStorageAuthenticationService", () => { "did:org:456", undefined, 60, - "read,write", 0 ); expect(mockAuthenticationRateService.check).toHaveBeenCalledWith( @@ -353,7 +344,6 @@ describe("EntityStorageAuthenticationService", () => { data: { organizationIdentity: "did:org:456", tenantId: undefined, - scope: ["read", "write"], version: 0 } }); @@ -371,8 +361,7 @@ describe("EntityStorageAuthenticationService", () => { payload: { sub: "did:user:123", org: "did:org:456", - tid: "tenant-1", - scope: "read,write" + tid: "tenant-1" } }; } @@ -418,8 +407,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:other", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); const createTokenSpy = vi.spyOn(TokenHelper, "createToken"); vi.spyOn(TokenHelper, "verify").mockImplementation( @@ -455,8 +443,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:other", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); const createTokenSpy = vi.spyOn(TokenHelper, "createToken"); vi.spyOn(TokenHelper, "verify").mockImplementation( @@ -493,7 +480,6 @@ describe("EntityStorageAuthenticationService", () => { organization: "did:org:456", password: "stored-password-hash", salt: "c2FsdA==", - scope: "read,write", passwordVersion: 2 }); const createTokenSpy = vi.spyOn(TokenHelper, "createToken"); @@ -527,8 +513,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); vi.spyOn(PasswordHelper, "updatePassword").mockResolvedValue(undefined); @@ -545,7 +530,7 @@ describe("EntityStorageAuthenticationService", () => { expect.objectContaining({ email: "user@example.com", identity: "did:user:123" }), "new-password", "current-password", - undefined + { minPasswordLength: undefined, maxPasswordLength: undefined } ); expect(mockAuthenticationRateService.clear).toHaveBeenCalledWith( "password-change", @@ -634,8 +619,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); const result = await service.login("user@example.com", "correct-password"); @@ -651,8 +635,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); }); @@ -679,8 +662,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:tenant-a", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); }); @@ -690,8 +672,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:tenant-b", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "write" + salt: "c2FsdA==" }); }); @@ -726,8 +707,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); await service.updatePassword("current-password", "new-password"); @@ -738,7 +718,7 @@ describe("EntityStorageAuthenticationService", () => { expect.objectContaining({ email: "user@example.com", identity: "did:user:123" }), "new-password", "current-password", - undefined + { minPasswordLength: undefined, maxPasswordLength: undefined } ); } ); @@ -751,8 +731,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); }); @@ -773,8 +752,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read" + salt: "c2FsdA==" }); }); @@ -786,7 +764,7 @@ describe("EntityStorageAuthenticationService", () => { } return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: TENANT_B, scope: "read" } + payload: { sub: "did:user:123", org: "did:org:456", tid: TENANT_B } }; } ); @@ -809,8 +787,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { @@ -823,8 +800,7 @@ describe("EntityStorageAuthenticationService", () => { payload: { sub: "did:user:123", org: "did:org:456", - tid: "tenant-a", - scope: "read,write" + tid: "tenant-a" } }; } @@ -844,8 +820,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); // Provide a tenantAdminComponent that can't find the tenant. @@ -875,8 +850,7 @@ describe("EntityStorageAuthenticationService", () => { payload: { sub: "did:user:123", org: "did:org:456", - tid: "tenant-a", - scope: "read,write" + tid: "tenant-a" } }; } @@ -897,8 +871,7 @@ describe("EntityStorageAuthenticationService", () => { identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", - salt: "c2FsdA==", - scope: "read,write" + salt: "c2FsdA==" }); const mockTenantAdminComponent = { @@ -925,8 +898,7 @@ describe("EntityStorageAuthenticationService", () => { payload: { sub: "did:user:123", org: "did:org:456", - tid: "tenant-a", - scope: "read,write" + tid: "tenant-a" } }; } @@ -949,7 +921,6 @@ describe("EntityStorageAuthenticationService", () => { "did:org:456", "tenant-a", 60, - "read,write", 0 ); }); diff --git a/packages/api-auth-models/LICENSE b/packages/api-auth-models/LICENSE new file mode 100644 index 0000000..677c34c --- /dev/null +++ b/packages/api-auth-models/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 IOTA Stiftung. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/api-auth-models/README.md b/packages/api-auth-models/README.md new file mode 100644 index 0000000..6383d07 --- /dev/null +++ b/packages/api-auth-models/README.md @@ -0,0 +1,25 @@ +# 3Sixty API Auth Models + +This package provides contracts for authentication audit and rate limiting. + +## Installation + +```shell +npm install @3sixty/api-auth-models +``` + +## Examples + +Usage of the APIs is shown in the examples [docs/examples.md](docs/examples.md) + +## Reference + +Detailed reference documentation for the API can be found in [docs/reference/index.md](docs/reference/index.md) + +## Changelog + +The changes between each version can be found in [docs/changelog.md](docs/changelog.md) + +## Origin + +This package is derived from the original [iotaledger/twin-api-auth](https://github.com/iotaledger/twin-api-auth/tree/feature/authorization/packages/api-auth-models) repository. diff --git a/packages/api-auth-models/docs/changelog.md b/packages/api-auth-models/docs/changelog.md new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/packages/api-auth-models/docs/changelog.md @@ -0,0 +1 @@ +# Changelog diff --git a/packages/api-auth-models/docs/examples.md b/packages/api-auth-models/docs/examples.md new file mode 100644 index 0000000..1a9da33 --- /dev/null +++ b/packages/api-auth-models/docs/examples.md @@ -0,0 +1,60 @@ +# Auth Models Examples + +Use these snippets to shape authentication audit entries, audit queries and rate limit configuration consistently across services and clients. + +## Audit Entries + +```typescript +import { AuthAuditEvent, type IAuthenticationAuditEntry } from '@3sixty/api-auth-models'; + +const entry: IAuthenticationAuditEntry = { + id: '018f2f67bb9d4a0caad8386f56df85ce', + event: AuthAuditEvent.LoginFailure, + dateCreated: '2026-04-13T10:00:00.000Z', + actorId: 'did:example:user:alice', + tenantId: 'did:example:tenant:alpha', + data: { + reason: 'invalid-password' + } +}; + +console.log(entry.event); // login-failure +``` + +## Audit Requests + +```typescript +import type { IAuditCreateRequest, IAuditQueryRequest } from '@3sixty/api-auth-models'; + +const createRequest: IAuditCreateRequest = { + body: { + event: 'account-locked', + actorId: 'did:example:user:alice' + } +}; + +const queryRequest: IAuditQueryRequest = { + query: { + actorId: 'did:example:user:alice', + startDate: '2026-04-01T00:00:00.000Z', + endDate: '2026-04-30T23:59:59.999Z', + limit: '25' + } +}; + +console.log(createRequest.body.event); // account-locked +console.log(queryRequest.query?.limit); // 25 +``` + +## Rate Limit Configuration + +```typescript +import type { IAuthenticationRateActionConfig } from '@3sixty/api-auth-models'; + +const loginLimit: IAuthenticationRateActionConfig = { + maxAttempts: 5, + windowMinutes: 15 +}; + +console.log(loginLimit.maxAttempts); // 5 +``` diff --git a/packages/api-auth-models/docs/reference/index.md b/packages/api-auth-models/docs/reference/index.md new file mode 100644 index 0000000..4c42231 --- /dev/null +++ b/packages/api-auth-models/docs/reference/index.md @@ -0,0 +1,23 @@ +# @twin.org/api-auth-models + +## Interfaces + +- [IAuthenticationAuditComponent](interfaces/IAuthenticationAuditComponent.md) +- [IAuthenticationAuditEntry](interfaces/IAuthenticationAuditEntry.md) +- [IAuthenticationRateActionConfig](interfaces/IAuthenticationRateActionConfig.md) +- [IAuthenticationRateComponent](interfaces/IAuthenticationRateComponent.md) +- [IAuditCreateRequest](interfaces/IAuditCreateRequest.md) +- [IAuditGetRequest](interfaces/IAuditGetRequest.md) +- [IAuditGetResponse](interfaces/IAuditGetResponse.md) +- [IAuditQueryRequest](interfaces/IAuditQueryRequest.md) +- [IAuditQueryResponse](interfaces/IAuditQueryResponse.md) +- [IAuditRemoveRequest](interfaces/IAuditRemoveRequest.md) +- [IAuditUpdateRequest](interfaces/IAuditUpdateRequest.md) + +## Type Aliases + +- [AuthAuditEvent](type-aliases/AuthAuditEvent.md) + +## Variables + +- [AuthAuditEvent](variables/AuthAuditEvent.md) diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditCreateRequest.md b/packages/api-auth-models/docs/reference/interfaces/IAuditCreateRequest.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditCreateRequest.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditCreateRequest.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditGetRequest.md b/packages/api-auth-models/docs/reference/interfaces/IAuditGetRequest.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditGetRequest.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditGetRequest.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditGetResponse.md b/packages/api-auth-models/docs/reference/interfaces/IAuditGetResponse.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditGetResponse.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditGetResponse.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditQueryRequest.md b/packages/api-auth-models/docs/reference/interfaces/IAuditQueryRequest.md similarity index 95% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditQueryRequest.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditQueryRequest.md index 310edbf..8bc5ed9 100644 --- a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditQueryRequest.md +++ b/packages/api-auth-models/docs/reference/interfaces/IAuditQueryRequest.md @@ -20,7 +20,7 @@ The actor identifier to filter by. > `optional` **organizationId?**: `string` -The organization identifier to filter by. +The organisation identifier to filter by. #### tenantId? diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditQueryResponse.md b/packages/api-auth-models/docs/reference/interfaces/IAuditQueryResponse.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditQueryResponse.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditQueryResponse.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditRemoveRequest.md b/packages/api-auth-models/docs/reference/interfaces/IAuditRemoveRequest.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditRemoveRequest.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditRemoveRequest.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditUpdateRequest.md b/packages/api-auth-models/docs/reference/interfaces/IAuditUpdateRequest.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuditUpdateRequest.md rename to packages/api-auth-models/docs/reference/interfaces/IAuditUpdateRequest.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationAuditComponent.md b/packages/api-auth-models/docs/reference/interfaces/IAuthenticationAuditComponent.md similarity index 97% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationAuditComponent.md rename to packages/api-auth-models/docs/reference/interfaces/IAuthenticationAuditComponent.md index 080aacb..15c55fb 100644 --- a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationAuditComponent.md +++ b/packages/api-auth-models/docs/reference/interfaces/IAuthenticationAuditComponent.md @@ -124,7 +124,7 @@ The actor identifier to filter the audit entries, optional. `string` -The organization identifier to filter the audit entries, optional. +The organisation identifier to filter the audit entries, optional. ###### tenantId? diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationAuditEntry.md b/packages/api-auth-models/docs/reference/interfaces/IAuthenticationAuditEntry.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationAuditEntry.md rename to packages/api-auth-models/docs/reference/interfaces/IAuthenticationAuditEntry.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationRateActionConfig.md b/packages/api-auth-models/docs/reference/interfaces/IAuthenticationRateActionConfig.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationRateActionConfig.md rename to packages/api-auth-models/docs/reference/interfaces/IAuthenticationRateActionConfig.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationRateComponent.md b/packages/api-auth-models/docs/reference/interfaces/IAuthenticationRateComponent.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/interfaces/IAuthenticationRateComponent.md rename to packages/api-auth-models/docs/reference/interfaces/IAuthenticationRateComponent.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/type-aliases/AuthAuditEvent.md b/packages/api-auth-models/docs/reference/type-aliases/AuthAuditEvent.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/type-aliases/AuthAuditEvent.md rename to packages/api-auth-models/docs/reference/type-aliases/AuthAuditEvent.md diff --git a/packages/api-auth-entity-storage-models/docs/reference/variables/AuthAuditEvent.md b/packages/api-auth-models/docs/reference/variables/AuthAuditEvent.md similarity index 100% rename from packages/api-auth-entity-storage-models/docs/reference/variables/AuthAuditEvent.md rename to packages/api-auth-models/docs/reference/variables/AuthAuditEvent.md diff --git a/packages/api-auth-models/locales/en.json b/packages/api-auth-models/locales/en.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/packages/api-auth-models/locales/en.json @@ -0,0 +1 @@ +{} diff --git a/packages/api-auth-models/package.json b/packages/api-auth-models/package.json new file mode 100644 index 0000000..fa044d0 --- /dev/null +++ b/packages/api-auth-models/package.json @@ -0,0 +1,82 @@ +{ + "name": "@3sixty/api-auth-models", + "version": "0.20.0-next.0", + "description": "Contracts for authentication audit and rate limiting.", + "repository": { + "type": "git", + "url": "git+https://github.com/3sixtyglobal/api-auth.git", + "directory": "packages/api-auth-models" + }, + "author": "martyn.janes@iota.org", + "license": "Apache-2.0", + "type": "module", + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "clean": "rimraf dist coverage .cache", + "build": "tstransform", + "dev": "nodemon --watch src --ext ts --exec \"pnpm run build\"", + "test": "vitest --run --config ./vitest.config.ts", + "test:build": "tstransform -p ./tests/tsconfig.json", + "validate-locales": "validate-locales", + "test:coverage": "vitest --run --coverage --config ./vitest.config.ts", + "docs:clean": "rimraf docs/reference", + "docs:generate": "typedoc --options typedoc.config.mjs", + "docs": "pnpm run docs:clean && pnpm run docs:generate", + "dist": "pnpm run clean && concurrently -g -n build,locales,types,test \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\" \"pnpm run test\"", + "dist:no-test": "pnpm run clean && concurrently -g -n build,locales,types \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\"" + }, + "dependencies": { + "@3sixty/core": "next", + "@3sixty/nameof": "next" + }, + "devDependencies": { + "@3sixty/nameof-transformer": "next", + "@3sixty/nameof-vitest-plugin": "next", + "@3sixty/validate-locales": "next", + "@types/node": "26.6.5", + "@vitest/coverage-v8": "5.0.5", + "concurrently": "10.0.6", + "copyfiles": "2.4.1", + "nodemon": "3.1.14", + "rimraf": "6.1.3", + "typedoc": "0.28.20", + "typedoc-plugin-markdown": "4.14.1", + "typescript": "6.0.3", + "vitest": "5.0.5" + }, + "main": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/es/index.js", + "default": "./dist/es/index.js" + } + }, + "files": [ + "dist/es", + "dist/types", + "locales", + "docs" + ], + "keywords": [ + "twin", + "trade", + "iota", + "framework", + "blockchain", + "api", + "auth", + "audit", + "rate", + "models", + "types", + "schemas" + ], + "bugs": { + "url": "git+https://github.com/3sixtyglobal/api-auth/issues" + }, + "homepage": "https://3sixty.global" +} diff --git a/packages/api-auth-models/src/index.ts b/packages/api-auth-models/src/index.ts new file mode 100644 index 0000000..208bef1 --- /dev/null +++ b/packages/api-auth-models/src/index.ts @@ -0,0 +1,14 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +export * from "./models/api/IAuditCreateRequest.js"; +export * from "./models/api/IAuditGetRequest.js"; +export * from "./models/api/IAuditGetResponse.js"; +export * from "./models/api/IAuditQueryRequest.js"; +export * from "./models/api/IAuditQueryResponse.js"; +export * from "./models/api/IAuditRemoveRequest.js"; +export * from "./models/api/IAuditUpdateRequest.js"; +export * from "./models/authAuditEvent.js"; +export * from "./models/IAuthenticationAuditComponent.js"; +export * from "./models/IAuthenticationAuditEntry.js"; +export * from "./models/IAuthenticationRateActionConfig.js"; +export * from "./models/IAuthenticationRateComponent.js"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAuditComponent.ts b/packages/api-auth-models/src/models/IAuthenticationAuditComponent.ts similarity index 97% rename from packages/api-auth-entity-storage-models/src/models/IAuthenticationAuditComponent.ts rename to packages/api-auth-models/src/models/IAuthenticationAuditComponent.ts index 76ae5e3..4550b91 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAuditComponent.ts +++ b/packages/api-auth-models/src/models/IAuthenticationAuditComponent.ts @@ -44,7 +44,7 @@ export interface IAuthenticationAuditComponent extends IComponent { * Query the audit entries. * @param options The query options. * @param options.actorId The actor identifier to filter the audit entries, optional. - * @param options.organizationId The organization identifier to filter the audit entries, optional. + * @param options.organizationId The organisation identifier to filter the audit entries, optional. * @param options.tenantId The tenant identifier to filter the audit entries, optional. * @param options.nodeId The node identifier to filter the audit entries, optional. * @param options.event The audit event to filter the audit entries, optional. diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAuditEntry.ts b/packages/api-auth-models/src/models/IAuthenticationAuditEntry.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/IAuthenticationAuditEntry.ts rename to packages/api-auth-models/src/models/IAuthenticationAuditEntry.ts diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationRateActionConfig.ts b/packages/api-auth-models/src/models/IAuthenticationRateActionConfig.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/IAuthenticationRateActionConfig.ts rename to packages/api-auth-models/src/models/IAuthenticationRateActionConfig.ts diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationRateComponent.ts b/packages/api-auth-models/src/models/IAuthenticationRateComponent.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/IAuthenticationRateComponent.ts rename to packages/api-auth-models/src/models/IAuthenticationRateComponent.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditCreateRequest.ts b/packages/api-auth-models/src/models/api/IAuditCreateRequest.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditCreateRequest.ts rename to packages/api-auth-models/src/models/api/IAuditCreateRequest.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditGetRequest.ts b/packages/api-auth-models/src/models/api/IAuditGetRequest.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditGetRequest.ts rename to packages/api-auth-models/src/models/api/IAuditGetRequest.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditGetResponse.ts b/packages/api-auth-models/src/models/api/IAuditGetResponse.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditGetResponse.ts rename to packages/api-auth-models/src/models/api/IAuditGetResponse.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditQueryRequest.ts b/packages/api-auth-models/src/models/api/IAuditQueryRequest.ts similarity index 95% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditQueryRequest.ts rename to packages/api-auth-models/src/models/api/IAuditQueryRequest.ts index faacff0..2b2490c 100644 --- a/packages/api-auth-entity-storage-models/src/models/api/IAuditQueryRequest.ts +++ b/packages/api-auth-models/src/models/api/IAuditQueryRequest.ts @@ -16,7 +16,7 @@ export interface IAuditQueryRequest { actorId?: string; /** - * The organization identifier to filter by. + * The organisation identifier to filter by. */ organizationId?: string; diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditQueryResponse.ts b/packages/api-auth-models/src/models/api/IAuditQueryResponse.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditQueryResponse.ts rename to packages/api-auth-models/src/models/api/IAuditQueryResponse.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditRemoveRequest.ts b/packages/api-auth-models/src/models/api/IAuditRemoveRequest.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditRemoveRequest.ts rename to packages/api-auth-models/src/models/api/IAuditRemoveRequest.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuditUpdateRequest.ts b/packages/api-auth-models/src/models/api/IAuditUpdateRequest.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuditUpdateRequest.ts rename to packages/api-auth-models/src/models/api/IAuditUpdateRequest.ts diff --git a/packages/api-auth-entity-storage-models/src/models/authAuditEvent.ts b/packages/api-auth-models/src/models/authAuditEvent.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/authAuditEvent.ts rename to packages/api-auth-models/src/models/authAuditEvent.ts diff --git a/packages/api-auth-models/tests/index.spec.ts b/packages/api-auth-models/tests/index.spec.ts new file mode 100644 index 0000000..64daa0f --- /dev/null +++ b/packages/api-auth-models/tests/index.spec.ts @@ -0,0 +1,6 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +describe("api-auth-models", () => { + test("This package currently has no tests", () => {}); +}); diff --git a/packages/api-auth-models/tests/tsconfig.json b/packages/api-auth-models/tests/tsconfig.json new file mode 100644 index 0000000..8f550b2 --- /dev/null +++ b/packages/api-auth-models/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "../.cache/tests.tsbuildinfo", + "rootDir": "..", + "types": ["vitest/globals"] + }, + "include": ["./**/*.ts"] +} diff --git a/packages/api-auth-models/tsconfig.json b/packages/api-auth-models/tsconfig.json new file mode 100644 index 0000000..a8a4cec --- /dev/null +++ b/packages/api-auth-models/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo", + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "ES2022", + "rootDir": "./src", + "outDir": "dist/es", + "strict": true, + "forceConsistentCasingInFileNames": true, + "stripInternal": true, + "noEmitHelpers": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "erasableSyntaxOnly": true, + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "declarationDir": "dist/types", + "types": ["node"], + "plugins": [{ "transform": "@3sixty/nameof-transformer" }], + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/api-auth-models/typedoc.config.mjs b/packages/api-auth-models/typedoc.config.mjs new file mode 100644 index 0000000..aad5b59 --- /dev/null +++ b/packages/api-auth-models/typedoc.config.mjs @@ -0,0 +1,24 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { OptionDefaults } from 'typedoc'; + +const customSchemaTags = ['@json-ld', '@json-schema']; + +export default { + entryPoints: ['src/index.ts'], + out: './docs/reference', + hideGenerator: true, + hideBreadcrumbs: true, + hidePageHeader: true, + excludeExternals: true, + githubPages: false, + disableSources: true, + sort: ['source-order'], + treatWarningsAsErrors: true, + readme: 'none', + entryFileName: 'index', + plugin: ['typedoc-plugin-markdown'], + blockTags: [...new Set([...OptionDefaults.blockTags, ...customSchemaTags])], + excludeTags: customSchemaTags, + useCustomAnchors: true +}; diff --git a/packages/api-auth-models/vitest.config.ts b/packages/api-auth-models/vitest.config.ts new file mode 100644 index 0000000..d40698f --- /dev/null +++ b/packages/api-auth-models/vitest.config.ts @@ -0,0 +1,24 @@ +// Copyright 2025 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { NameOfPlugin } from "@3sixty/nameof-vitest-plugin"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [NameOfPlugin], + test: { + include: ["./tests/**/*.spec.ts"], + globals: true, + cache: false, + testTimeout: 300000, + hookTimeout: 300000, + bail: 1, + reporters: ["verbose"], + disableConsoleIntercept: true, + coverage: { + reporter: ["text", "lcov"], + include: ["src/**/*.ts"], + exclude: ["**/index.ts", "**/models/**/*.ts"] + }, + fileParallelism: false + } +}); diff --git a/packages/api-auth-rest-client/LICENSE b/packages/api-auth-rest-client/LICENSE new file mode 100644 index 0000000..677c34c --- /dev/null +++ b/packages/api-auth-rest-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 IOTA Stiftung. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/api-auth-rest-client/README.md b/packages/api-auth-rest-client/README.md new file mode 100644 index 0000000..5264783 --- /dev/null +++ b/packages/api-auth-rest-client/README.md @@ -0,0 +1,25 @@ +# 3Sixty API Auth REST Client + +This package provides a REST client for the authentication audit endpoints. + +## Installation + +```shell +npm install @3sixty/api-auth-rest-client +``` + +## Examples + +Usage of the APIs is shown in the examples [docs/examples.md](docs/examples.md) + +## Reference + +Detailed reference documentation for the API can be found in [docs/reference/index.md](docs/reference/index.md) + +## Changelog + +The changes between each version can be found in [docs/changelog.md](docs/changelog.md) + +## Origin + +This package is derived from the original [iotaledger/twin-api-auth](https://github.com/iotaledger/twin-api-auth/tree/feature/authorization/packages/api-auth-rest-client) repository. diff --git a/packages/api-auth-rest-client/docs/changelog.md b/packages/api-auth-rest-client/docs/changelog.md new file mode 100644 index 0000000..8ea399e --- /dev/null +++ b/packages/api-auth-rest-client/docs/changelog.md @@ -0,0 +1,1553 @@ +# Changelog + +## [0.9.2-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.6...api-auth-entity-storage-rest-client-v0.9.2-next.7) (2026-08-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.6 to 0.9.2-next.7 + * @twin.org/api-core bumped from 0.9.2-next.6 to 0.9.2-next.7 + * @twin.org/api-models bumped from 0.9.2-next.6 to 0.9.2-next.7 + +## [0.9.2-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.5...api-auth-entity-storage-rest-client-v0.9.2-next.6) (2026-08-07) + + +### Features + +* health updates ([#243](https://github.com/iotaledger/twin-api/issues/243)) ([8360945](https://github.com/iotaledger/twin-api/commit/836094547839f63f2309d4bcd8c4d19128c73883)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.5 to 0.9.2-next.6 + * @twin.org/api-core bumped from 0.9.2-next.5 to 0.9.2-next.6 + * @twin.org/api-models bumped from 0.9.2-next.5 to 0.9.2-next.6 + +## [0.9.2-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.4...api-auth-entity-storage-rest-client-v0.9.2-next.5) (2026-08-03) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.4 to 0.9.2-next.5 + * @twin.org/api-core bumped from 0.9.2-next.4 to 0.9.2-next.5 + * @twin.org/api-models bumped from 0.9.2-next.4 to 0.9.2-next.5 + +## [0.9.2-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.3...api-auth-entity-storage-rest-client-v0.9.2-next.4) (2026-08-02) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.3 to 0.9.2-next.4 + * @twin.org/api-core bumped from 0.9.2-next.3 to 0.9.2-next.4 + * @twin.org/api-models bumped from 0.9.2-next.3 to 0.9.2-next.4 + +## [0.9.2-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.2...api-auth-entity-storage-rest-client-v0.9.2-next.3) (2026-07-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.2 to 0.9.2-next.3 + * @twin.org/api-core bumped from 0.9.2-next.2 to 0.9.2-next.3 + * @twin.org/api-models bumped from 0.9.2-next.2 to 0.9.2-next.3 + +## [0.9.2-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.1...api-auth-entity-storage-rest-client-v0.9.2-next.2) (2026-07-29) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.1 to 0.9.2-next.2 + * @twin.org/api-core bumped from 0.9.2-next.1 to 0.9.2-next.2 + * @twin.org/api-models bumped from 0.9.2-next.1 to 0.9.2-next.2 + +## [0.9.2-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.2-next.0...api-auth-entity-storage-rest-client-v0.9.2-next.1) (2026-07-28) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* enhanced rest handling ([#208](https://github.com/iotaledger/twin-api/issues/208)) ([99d5f3f](https://github.com/iotaledger/twin-api/commit/99d5f3f96d262e57828d98d3f3b1e3da8a863378)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* improved rest handling ([#211](https://github.com/iotaledger/twin-api/issues/211)) ([12c50ea](https://github.com/iotaledger/twin-api/commit/12c50ea7655276eed7a73d311ac5286476c98201)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* remove authentication generators ([#66](https://github.com/iotaledger/twin-api/issues/66)) ([adaa169](https://github.com/iotaledger/twin-api/commit/adaa1698df1c5ccb0ad645a7a7c0d3ef82ef6ac1)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.0 to 0.9.2-next.1 + * @twin.org/api-core bumped from 0.9.2-next.0 to 0.9.2-next.1 + * @twin.org/api-models bumped from 0.9.2-next.0 to 0.9.2-next.1 + +## [0.9.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1...api-auth-entity-storage-rest-client-v0.9.1) (2026-07-27) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) +* release to production ([#195](https://github.com/iotaledger/twin-api/issues/195)) ([a3f5c1f](https://github.com/iotaledger/twin-api/commit/a3f5c1fc35a748762af7efa4f7f95776004d1309)) +* release to production ([#197](https://github.com/iotaledger/twin-api/issues/197)) ([f04c156](https://github.com/iotaledger/twin-api/commit/f04c1567f801cde36c5ec8595f9b9369109d9e42)) +* release to production ([#201](https://github.com/iotaledger/twin-api/issues/201)) ([e1c46fd](https://github.com/iotaledger/twin-api/commit/e1c46fd02c1f4d44d5393e2f49a24f1e4468f240)) +* release to production ([#224](https://github.com/iotaledger/twin-api/issues/224)) ([dffaf08](https://github.com/iotaledger/twin-api/commit/dffaf082b7dccc6f57a5b7cd20b95d24bb8ec2f3)) + +## [0.9.1-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.8...api-auth-entity-storage-rest-client-v0.9.1-next.9) (2026-07-23) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.8 to 0.9.1-next.9 + * @twin.org/api-core bumped from 0.9.1-next.8 to 0.9.1-next.9 + * @twin.org/api-models bumped from 0.9.1-next.8 to 0.9.1-next.9 + +## [0.9.1-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.7...api-auth-entity-storage-rest-client-v0.9.1-next.8) (2026-07-03) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.7 to 0.9.1-next.8 + * @twin.org/api-core bumped from 0.9.1-next.7 to 0.9.1-next.8 + * @twin.org/api-models bumped from 0.9.1-next.7 to 0.9.1-next.8 + +## [0.9.1-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.6...api-auth-entity-storage-rest-client-v0.9.1-next.7) (2026-07-02) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.6 to 0.9.1-next.7 + * @twin.org/api-core bumped from 0.9.1-next.6 to 0.9.1-next.7 + * @twin.org/api-models bumped from 0.9.1-next.6 to 0.9.1-next.7 + +## [0.9.1-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.5...api-auth-entity-storage-rest-client-v0.9.1-next.6) (2026-06-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.5 to 0.9.1-next.6 + * @twin.org/api-core bumped from 0.9.1-next.5 to 0.9.1-next.6 + * @twin.org/api-models bumped from 0.9.1-next.5 to 0.9.1-next.6 + +## [0.9.1-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.4...api-auth-entity-storage-rest-client-v0.9.1-next.5) (2026-06-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.4 to 0.9.1-next.5 + * @twin.org/api-core bumped from 0.9.1-next.4 to 0.9.1-next.5 + * @twin.org/api-models bumped from 0.9.1-next.4 to 0.9.1-next.5 + +## [0.9.1-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.3...api-auth-entity-storage-rest-client-v0.9.1-next.4) (2026-06-30) + + +### Features + +* improved rest handling ([#211](https://github.com/iotaledger/twin-api/issues/211)) ([12c50ea](https://github.com/iotaledger/twin-api/commit/12c50ea7655276eed7a73d311ac5286476c98201)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.3 to 0.9.1-next.4 + * @twin.org/api-core bumped from 0.9.1-next.3 to 0.9.1-next.4 + * @twin.org/api-models bumped from 0.9.1-next.3 to 0.9.1-next.4 + +## [0.9.1-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.2...api-auth-entity-storage-rest-client-v0.9.1-next.3) (2026-06-29) + + +### Features + +* enhanced rest handling ([#208](https://github.com/iotaledger/twin-api/issues/208)) ([99d5f3f](https://github.com/iotaledger/twin-api/commit/99d5f3f96d262e57828d98d3f3b1e3da8a863378)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.2 to 0.9.1-next.3 + * @twin.org/api-core bumped from 0.9.1-next.2 to 0.9.1-next.3 + * @twin.org/api-models bumped from 0.9.1-next.2 to 0.9.1-next.3 + +## [0.9.1-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.1...api-auth-entity-storage-rest-client-v0.9.1-next.2) (2026-06-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.1 to 0.9.1-next.2 + * @twin.org/api-core bumped from 0.9.1-next.1 to 0.9.1-next.2 + * @twin.org/api-models bumped from 0.9.1-next.1 to 0.9.1-next.2 + +## [0.9.1-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.1-next.0...api-auth-entity-storage-rest-client-v0.9.1-next.1) (2026-06-26) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* remove authentication generators ([#66](https://github.com/iotaledger/twin-api/issues/66)) ([adaa169](https://github.com/iotaledger/twin-api/commit/adaa1698df1c5ccb0ad645a7a7c0d3ef82ef6ac1)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.0 to 0.9.1-next.1 + * @twin.org/api-core bumped from 0.9.1-next.0 to 0.9.1-next.1 + * @twin.org/api-models bumped from 0.9.1-next.0 to 0.9.1-next.1 + +## [0.9.0](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.0...api-auth-entity-storage-rest-client-v0.9.0) (2026-06-24) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) +* release to production ([#195](https://github.com/iotaledger/twin-api/issues/195)) ([a3f5c1f](https://github.com/iotaledger/twin-api/commit/a3f5c1fc35a748762af7efa4f7f95776004d1309)) +* release to production ([#197](https://github.com/iotaledger/twin-api/issues/197)) ([f04c156](https://github.com/iotaledger/twin-api/commit/f04c1567f801cde36c5ec8595f9b9369109d9e42)) +* release to production ([#201](https://github.com/iotaledger/twin-api/issues/201)) ([e1c46fd](https://github.com/iotaledger/twin-api/commit/e1c46fd02c1f4d44d5393e2f49a24f1e4468f240)) + +## [0.9.0-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.9.0-next.0...api-auth-entity-storage-rest-client-v0.9.0-next.1) (2026-06-23) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* remove authentication generators ([#66](https://github.com/iotaledger/twin-api/issues/66)) ([adaa169](https://github.com/iotaledger/twin-api/commit/adaa1698df1c5ccb0ad645a7a7c0d3ef82ef6ac1)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.0-next.0 to 0.9.0-next.1 + * @twin.org/api-core bumped from 0.9.0-next.0 to 0.9.0-next.1 + * @twin.org/api-models bumped from 0.9.0-next.0 to 0.9.0-next.1 + +## [0.0.3-next.53](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.52...api-auth-entity-storage-rest-client-v0.0.3-next.53) (2026-06-23) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.52 to 0.0.3-next.53 + * @twin.org/api-core bumped from 0.0.3-next.52 to 0.0.3-next.53 + * @twin.org/api-models bumped from 0.0.3-next.52 to 0.0.3-next.53 + +## [0.0.3-next.52](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.51...api-auth-entity-storage-rest-client-v0.0.3-next.52) (2026-06-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.51 to 0.0.3-next.52 + * @twin.org/api-core bumped from 0.0.3-next.51 to 0.0.3-next.52 + * @twin.org/api-models bumped from 0.0.3-next.51 to 0.0.3-next.52 + +## [0.0.3-next.51](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.50...api-auth-entity-storage-rest-client-v0.0.3-next.51) (2026-06-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.50 to 0.0.3-next.51 + * @twin.org/api-core bumped from 0.0.3-next.50 to 0.0.3-next.51 + * @twin.org/api-models bumped from 0.0.3-next.50 to 0.0.3-next.51 + +## [0.0.3-next.50](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.49...api-auth-entity-storage-rest-client-v0.0.3-next.50) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.49 to 0.0.3-next.50 + * @twin.org/api-core bumped from 0.0.3-next.49 to 0.0.3-next.50 + * @twin.org/api-models bumped from 0.0.3-next.49 to 0.0.3-next.50 + +## [0.0.3-next.49](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.48...api-auth-entity-storage-rest-client-v0.0.3-next.49) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.48 to 0.0.3-next.49 + * @twin.org/api-core bumped from 0.0.3-next.48 to 0.0.3-next.49 + * @twin.org/api-models bumped from 0.0.3-next.48 to 0.0.3-next.49 + +## [0.0.3-next.48](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.47...api-auth-entity-storage-rest-client-v0.0.3-next.48) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.47 to 0.0.3-next.48 + * @twin.org/api-core bumped from 0.0.3-next.47 to 0.0.3-next.48 + * @twin.org/api-models bumped from 0.0.3-next.47 to 0.0.3-next.48 + +## [0.0.3-next.47](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.46...api-auth-entity-storage-rest-client-v0.0.3-next.47) (2026-06-18) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.46 to 0.0.3-next.47 + * @twin.org/api-core bumped from 0.0.3-next.46 to 0.0.3-next.47 + * @twin.org/api-models bumped from 0.0.3-next.46 to 0.0.3-next.47 + +## [0.0.3-next.46](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.45...api-auth-entity-storage-rest-client-v0.0.3-next.46) (2026-06-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.45 to 0.0.3-next.46 + * @twin.org/api-core bumped from 0.0.3-next.45 to 0.0.3-next.46 + * @twin.org/api-models bumped from 0.0.3-next.45 to 0.0.3-next.46 + +## [0.0.3-next.45](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.44...api-auth-entity-storage-rest-client-v0.0.3-next.45) (2026-06-15) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.44 to 0.0.3-next.45 + * @twin.org/api-core bumped from 0.0.3-next.44 to 0.0.3-next.45 + * @twin.org/api-models bumped from 0.0.3-next.44 to 0.0.3-next.45 + +## [0.0.3-next.44](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.43...api-auth-entity-storage-rest-client-v0.0.3-next.44) (2026-06-11) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.43 to 0.0.3-next.44 + * @twin.org/api-core bumped from 0.0.3-next.43 to 0.0.3-next.44 + * @twin.org/api-models bumped from 0.0.3-next.43 to 0.0.3-next.44 + +## [0.0.3-next.43](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.42...api-auth-entity-storage-rest-client-v0.0.3-next.43) (2026-06-10) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.42 to 0.0.3-next.43 + * @twin.org/api-core bumped from 0.0.3-next.42 to 0.0.3-next.43 + * @twin.org/api-models bumped from 0.0.3-next.42 to 0.0.3-next.43 + +## [0.0.3-next.42](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.41...api-auth-entity-storage-rest-client-v0.0.3-next.42) (2026-06-08) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.41 to 0.0.3-next.42 + * @twin.org/api-core bumped from 0.0.3-next.41 to 0.0.3-next.42 + * @twin.org/api-models bumped from 0.0.3-next.41 to 0.0.3-next.42 + +## [0.0.3-next.41](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.40...api-auth-entity-storage-rest-client-v0.0.3-next.41) (2026-06-05) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.40 to 0.0.3-next.41 + * @twin.org/api-core bumped from 0.0.3-next.40 to 0.0.3-next.41 + * @twin.org/api-models bumped from 0.0.3-next.40 to 0.0.3-next.41 + +## [0.0.3-next.40](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.39...api-auth-entity-storage-rest-client-v0.0.3-next.40) (2026-06-04) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.39 to 0.0.3-next.40 + * @twin.org/api-core bumped from 0.0.3-next.39 to 0.0.3-next.40 + * @twin.org/api-models bumped from 0.0.3-next.39 to 0.0.3-next.40 + +## [0.0.3-next.39](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.38...api-auth-entity-storage-rest-client-v0.0.3-next.39) (2026-06-02) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.38 to 0.0.3-next.39 + * @twin.org/api-core bumped from 0.0.3-next.38 to 0.0.3-next.39 + * @twin.org/api-models bumped from 0.0.3-next.38 to 0.0.3-next.39 + +## [0.0.3-next.38](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.37...api-auth-entity-storage-rest-client-v0.0.3-next.38) (2026-05-28) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.37 to 0.0.3-next.38 + * @twin.org/api-core bumped from 0.0.3-next.37 to 0.0.3-next.38 + * @twin.org/api-models bumped from 0.0.3-next.37 to 0.0.3-next.38 + +## [0.0.3-next.37](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.36...api-auth-entity-storage-rest-client-v0.0.3-next.37) (2026-05-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.36 to 0.0.3-next.37 + * @twin.org/api-core bumped from 0.0.3-next.36 to 0.0.3-next.37 + * @twin.org/api-models bumped from 0.0.3-next.36 to 0.0.3-next.37 + +## [0.0.3-next.36](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.35...api-auth-entity-storage-rest-client-v0.0.3-next.36) (2026-05-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.35 to 0.0.3-next.36 + * @twin.org/api-core bumped from 0.0.3-next.35 to 0.0.3-next.36 + * @twin.org/api-models bumped from 0.0.3-next.35 to 0.0.3-next.36 + +## [0.0.3-next.35](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.34...api-auth-entity-storage-rest-client-v0.0.3-next.35) (2026-05-21) + + +### Features + +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.34 to 0.0.3-next.35 + * @twin.org/api-core bumped from 0.0.3-next.34 to 0.0.3-next.35 + * @twin.org/api-models bumped from 0.0.3-next.34 to 0.0.3-next.35 + +## [0.0.3-next.34](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.33...api-auth-entity-storage-rest-client-v0.0.3-next.34) (2026-05-19) + + +### Features + +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.33 to 0.0.3-next.34 + * @twin.org/api-core bumped from 0.0.3-next.33 to 0.0.3-next.34 + * @twin.org/api-models bumped from 0.0.3-next.33 to 0.0.3-next.34 + +## [0.0.3-next.33](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.32...api-auth-entity-storage-rest-client-v0.0.3-next.33) (2026-05-11) + + +### Features + +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.32 to 0.0.3-next.33 + * @twin.org/api-core bumped from 0.0.3-next.32 to 0.0.3-next.33 + * @twin.org/api-models bumped from 0.0.3-next.32 to 0.0.3-next.33 + +## [0.0.3-next.32](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.31...api-auth-entity-storage-rest-client-v0.0.3-next.32) (2026-05-07) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.31 to 0.0.3-next.32 + * @twin.org/api-core bumped from 0.0.3-next.31 to 0.0.3-next.32 + * @twin.org/api-models bumped from 0.0.3-next.31 to 0.0.3-next.32 + +## [0.0.3-next.31](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.30...api-auth-entity-storage-rest-client-v0.0.3-next.31) (2026-05-06) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.30 to 0.0.3-next.31 + * @twin.org/api-core bumped from 0.0.3-next.30 to 0.0.3-next.31 + * @twin.org/api-models bumped from 0.0.3-next.30 to 0.0.3-next.31 + +## [0.0.3-next.30](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.29...api-auth-entity-storage-rest-client-v0.0.3-next.30) (2026-05-05) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.29 to 0.0.3-next.30 + * @twin.org/api-core bumped from 0.0.3-next.29 to 0.0.3-next.30 + * @twin.org/api-models bumped from 0.0.3-next.29 to 0.0.3-next.30 + +## [0.0.3-next.29](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.28...api-auth-entity-storage-rest-client-v0.0.3-next.29) (2026-05-01) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.28 to 0.0.3-next.29 + * @twin.org/api-core bumped from 0.0.3-next.28 to 0.0.3-next.29 + * @twin.org/api-models bumped from 0.0.3-next.28 to 0.0.3-next.29 + +## [0.0.3-next.28](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.27...api-auth-entity-storage-rest-client-v0.0.3-next.28) (2026-04-30) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.27 to 0.0.3-next.28 + * @twin.org/api-core bumped from 0.0.3-next.27 to 0.0.3-next.28 + * @twin.org/api-models bumped from 0.0.3-next.27 to 0.0.3-next.28 + +## [0.0.3-next.27](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.26...api-auth-entity-storage-rest-client-v0.0.3-next.27) (2026-04-23) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.26 to 0.0.3-next.27 + * @twin.org/api-core bumped from 0.0.3-next.26 to 0.0.3-next.27 + * @twin.org/api-models bumped from 0.0.3-next.26 to 0.0.3-next.27 + +## [0.0.3-next.26](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.25...api-auth-entity-storage-rest-client-v0.0.3-next.26) (2026-04-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.25 to 0.0.3-next.26 + * @twin.org/api-core bumped from 0.0.3-next.25 to 0.0.3-next.26 + * @twin.org/api-models bumped from 0.0.3-next.25 to 0.0.3-next.26 + +## [0.0.3-next.25](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.24...api-auth-entity-storage-rest-client-v0.0.3-next.25) (2026-04-14) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.24 to 0.0.3-next.25 + * @twin.org/api-core bumped from 0.0.3-next.24 to 0.0.3-next.25 + * @twin.org/api-models bumped from 0.0.3-next.24 to 0.0.3-next.25 + +## [0.0.3-next.24](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.23...api-auth-entity-storage-rest-client-v0.0.3-next.24) (2026-04-14) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.23 to 0.0.3-next.24 + * @twin.org/api-core bumped from 0.0.3-next.23 to 0.0.3-next.24 + * @twin.org/api-models bumped from 0.0.3-next.23 to 0.0.3-next.24 + +## [0.0.3-next.23](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.22...api-auth-entity-storage-rest-client-v0.0.3-next.23) (2026-04-14) + + +### Features + +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.22 to 0.0.3-next.23 + * @twin.org/api-core bumped from 0.0.3-next.22 to 0.0.3-next.23 + * @twin.org/api-models bumped from 0.0.3-next.22 to 0.0.3-next.23 + +## [0.0.3-next.22](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.21...api-auth-entity-storage-rest-client-v0.0.3-next.22) (2026-03-27) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.21 to 0.0.3-next.22 + * @twin.org/api-core bumped from 0.0.3-next.21 to 0.0.3-next.22 + * @twin.org/api-models bumped from 0.0.3-next.21 to 0.0.3-next.22 + +## [0.0.3-next.21](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.20...api-auth-entity-storage-rest-client-v0.0.3-next.21) (2026-03-11) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.20 to 0.0.3-next.21 + * @twin.org/api-core bumped from 0.0.3-next.20 to 0.0.3-next.21 + * @twin.org/api-models bumped from 0.0.3-next.20 to 0.0.3-next.21 + +## [0.0.3-next.20](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.19...api-auth-entity-storage-rest-client-v0.0.3-next.20) (2026-02-09) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.19 to 0.0.3-next.20 + * @twin.org/api-core bumped from 0.0.3-next.19 to 0.0.3-next.20 + * @twin.org/api-models bumped from 0.0.3-next.19 to 0.0.3-next.20 + +## [0.0.3-next.19](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.18...api-auth-entity-storage-rest-client-v0.0.3-next.19) (2026-02-06) + + +### Features + +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.18 to 0.0.3-next.19 + * @twin.org/api-core bumped from 0.0.3-next.18 to 0.0.3-next.19 + * @twin.org/api-models bumped from 0.0.3-next.18 to 0.0.3-next.19 + +## [0.0.3-next.18](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.17...api-auth-entity-storage-rest-client-v0.0.3-next.18) (2026-02-04) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.17 to 0.0.3-next.18 + * @twin.org/api-core bumped from 0.0.3-next.17 to 0.0.3-next.18 + * @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18 + +## [0.0.3-next.17](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.16...api-auth-entity-storage-rest-client-v0.0.3-next.17) (2026-01-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.16 to 0.0.3-next.17 + * @twin.org/api-core bumped from 0.0.3-next.16 to 0.0.3-next.17 + * @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17 + +## [0.0.3-next.16](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.15...api-auth-entity-storage-rest-client-v0.0.3-next.16) (2026-01-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.15 to 0.0.3-next.16 + * @twin.org/api-core bumped from 0.0.3-next.15 to 0.0.3-next.16 + * @twin.org/api-models bumped from 0.0.3-next.15 to 0.0.3-next.16 + +## [0.0.3-next.15](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.14...api-auth-entity-storage-rest-client-v0.0.3-next.15) (2026-01-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.14 to 0.0.3-next.15 + * @twin.org/api-core bumped from 0.0.3-next.14 to 0.0.3-next.15 + * @twin.org/api-models bumped from 0.0.3-next.14 to 0.0.3-next.15 + +## [0.0.3-next.14](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.13...api-auth-entity-storage-rest-client-v0.0.3-next.14) (2026-01-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.13 to 0.0.3-next.14 + * @twin.org/api-core bumped from 0.0.3-next.13 to 0.0.3-next.14 + * @twin.org/api-models bumped from 0.0.3-next.13 to 0.0.3-next.14 + +## [0.0.3-next.13](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.12...api-auth-entity-storage-rest-client-v0.0.3-next.13) (2026-01-19) + + +### Features + +* remove authentication generators ([#66](https://github.com/iotaledger/twin-api/issues/66)) ([adaa169](https://github.com/iotaledger/twin-api/commit/adaa1698df1c5ccb0ad645a7a7c0d3ef82ef6ac1)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.12 to 0.0.3-next.13 + * @twin.org/api-core bumped from 0.0.3-next.12 to 0.0.3-next.13 + * @twin.org/api-models bumped from 0.0.3-next.12 to 0.0.3-next.13 + +## [0.0.3-next.12](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.11...api-auth-entity-storage-rest-client-v0.0.3-next.12) (2026-01-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.11 to 0.0.3-next.12 + * @twin.org/api-core bumped from 0.0.3-next.11 to 0.0.3-next.12 + * @twin.org/api-models bumped from 0.0.3-next.11 to 0.0.3-next.12 + +## [0.0.3-next.11](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.10...api-auth-entity-storage-rest-client-v0.0.3-next.11) (2026-01-08) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.10 to 0.0.3-next.11 + * @twin.org/api-core bumped from 0.0.3-next.10 to 0.0.3-next.11 + * @twin.org/api-models bumped from 0.0.3-next.10 to 0.0.3-next.11 + +## [0.0.3-next.10](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.9...api-auth-entity-storage-rest-client-v0.0.3-next.10) (2026-01-05) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.9 to 0.0.3-next.10 + * @twin.org/api-core bumped from 0.0.3-next.9 to 0.0.3-next.10 + * @twin.org/api-models bumped from 0.0.3-next.9 to 0.0.3-next.10 + +## [0.0.3-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.8...api-auth-entity-storage-rest-client-v0.0.3-next.9) (2026-01-05) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Bug Fixes + +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.8 to 0.0.3-next.9 + * @twin.org/api-core bumped from 0.0.3-next.8 to 0.0.3-next.9 + * @twin.org/api-models bumped from 0.0.3-next.8 to 0.0.3-next.9 + +## [0.0.3-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.7...api-auth-entity-storage-rest-client-v0.0.3-next.8) (2025-12-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.7 to 0.0.3-next.8 + * @twin.org/api-core bumped from 0.0.3-next.7 to 0.0.3-next.8 + * @twin.org/api-models bumped from 0.0.3-next.7 to 0.0.3-next.8 + +## [0.0.3-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.6...api-auth-entity-storage-rest-client-v0.0.3-next.7) (2025-11-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.6 to 0.0.3-next.7 + * @twin.org/api-core bumped from 0.0.3-next.6 to 0.0.3-next.7 + * @twin.org/api-models bumped from 0.0.3-next.6 to 0.0.3-next.7 + +## [0.0.3-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.5...api-auth-entity-storage-rest-client-v0.0.3-next.6) (2025-11-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.5 to 0.0.3-next.6 + * @twin.org/api-core bumped from 0.0.3-next.5 to 0.0.3-next.6 + * @twin.org/api-models bumped from 0.0.3-next.5 to 0.0.3-next.6 + +## [0.0.3-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.4...api-auth-entity-storage-rest-client-v0.0.3-next.5) (2025-11-14) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.4 to 0.0.3-next.5 + * @twin.org/api-core bumped from 0.0.3-next.4 to 0.0.3-next.5 + * @twin.org/api-models bumped from 0.0.3-next.4 to 0.0.3-next.5 + +## [0.0.3-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.3...api-auth-entity-storage-rest-client-v0.0.3-next.4) (2025-11-14) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Bug Fixes + +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.3 to 0.0.3-next.4 + * @twin.org/api-core bumped from 0.0.3-next.3 to 0.0.3-next.4 + * @twin.org/api-models bumped from 0.0.3-next.3 to 0.0.3-next.4 + +## [0.0.3-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.2...api-auth-entity-storage-rest-client-v0.0.3-next.3) (2025-11-14) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.2 to 0.0.3-next.3 + * @twin.org/api-core bumped from 0.0.3-next.2 to 0.0.3-next.3 + * @twin.org/api-models bumped from 0.0.3-next.2 to 0.0.3-next.3 + +## [0.0.3-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.1...api-auth-entity-storage-rest-client-v0.0.3-next.2) (2025-11-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.1 to 0.0.3-next.2 + * @twin.org/api-core bumped from 0.0.3-next.1 to 0.0.3-next.2 + * @twin.org/api-models bumped from 0.0.3-next.1 to 0.0.3-next.2 + +## [0.0.3-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.3-next.0...api-auth-entity-storage-rest-client-v0.0.3-next.1) (2025-11-10) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Bug Fixes + +* use correct format for log messaging ([44bc2a4](https://github.com/iotaledger/twin-api/commit/44bc2a4f7cf1f9c38a7e8c6f90ccb2424c958de9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.0 to 0.0.3-next.1 + * @twin.org/api-core bumped from 0.0.3-next.0 to 0.0.3-next.1 + * @twin.org/api-models bumped from 0.0.3-next.0 to 0.0.3-next.1 + +## [0.0.2-next.13](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.12...api-auth-entity-storage-rest-client-v0.0.2-next.13) (2025-10-09) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.12 to 0.0.2-next.13 + * @twin.org/api-core bumped from 0.0.2-next.12 to 0.0.2-next.13 + * @twin.org/api-models bumped from 0.0.2-next.12 to 0.0.2-next.13 + +## [0.0.2-next.12](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.11...api-auth-entity-storage-rest-client-v0.0.2-next.12) (2025-10-09) + + +### Features + +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.11 to 0.0.2-next.12 + * @twin.org/api-core bumped from 0.0.2-next.11 to 0.0.2-next.12 + * @twin.org/api-models bumped from 0.0.2-next.11 to 0.0.2-next.12 + +## [0.0.2-next.11](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.10...api-auth-entity-storage-rest-client-v0.0.2-next.11) (2025-09-29) + + +### Features + +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.10 to 0.0.2-next.11 + * @twin.org/api-core bumped from 0.0.2-next.10 to 0.0.2-next.11 + * @twin.org/api-models bumped from 0.0.2-next.10 to 0.0.2-next.11 + +## [0.0.2-next.10](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.9...api-auth-entity-storage-rest-client-v0.0.2-next.10) (2025-09-23) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.9 to 0.0.2-next.10 + * @twin.org/api-core bumped from 0.0.2-next.9 to 0.0.2-next.10 + * @twin.org/api-models bumped from 0.0.2-next.9 to 0.0.2-next.10 + +## [0.0.2-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.8...api-auth-entity-storage-rest-client-v0.0.2-next.9) (2025-08-29) + + +### Features + +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.8 to 0.0.2-next.9 + * @twin.org/api-core bumped from 0.0.2-next.8 to 0.0.2-next.9 + * @twin.org/api-models bumped from 0.0.2-next.8 to 0.0.2-next.9 + +## [0.0.2-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.7...api-auth-entity-storage-rest-client-v0.0.2-next.8) (2025-08-21) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.7 to 0.0.2-next.8 + * @twin.org/api-core bumped from 0.0.2-next.7 to 0.0.2-next.8 + * @twin.org/api-models bumped from 0.0.2-next.7 to 0.0.2-next.8 + +## [0.0.2-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.6...api-auth-entity-storage-rest-client-v0.0.2-next.7) (2025-08-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.6 to 0.0.2-next.7 + * @twin.org/api-core bumped from 0.0.2-next.6 to 0.0.2-next.7 + * @twin.org/api-models bumped from 0.0.2-next.6 to 0.0.2-next.7 + +## [0.0.2-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.5...api-auth-entity-storage-rest-client-v0.0.2-next.6) (2025-08-19) + + +### Features + +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.5 to 0.0.2-next.6 + * @twin.org/api-core bumped from 0.0.2-next.5 to 0.0.2-next.6 + * @twin.org/api-models bumped from 0.0.2-next.5 to 0.0.2-next.6 + +## [0.0.2-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.4...api-auth-entity-storage-rest-client-v0.0.2-next.5) (2025-07-25) + + +### Features + +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.4 to 0.0.2-next.5 + * @twin.org/api-core bumped from 0.0.2-next.4 to 0.0.2-next.5 + * @twin.org/api-models bumped from 0.0.2-next.4 to 0.0.2-next.5 + +## [0.0.2-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.3...api-auth-entity-storage-rest-client-v0.0.2-next.4) (2025-07-25) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.3 to 0.0.2-next.4 + * @twin.org/api-core bumped from 0.0.2-next.3 to 0.0.2-next.4 + * @twin.org/api-models bumped from 0.0.2-next.3 to 0.0.2-next.4 + +## [0.0.2-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.2...api-auth-entity-storage-rest-client-v0.0.2-next.3) (2025-07-24) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.2 to 0.0.2-next.3 + * @twin.org/api-core bumped from 0.0.2-next.2 to 0.0.2-next.3 + * @twin.org/api-models bumped from 0.0.2-next.2 to 0.0.2-next.3 + +## [0.0.2-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.1...api-auth-entity-storage-rest-client-v0.0.2-next.2) (2025-07-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.1 to 0.0.2-next.2 + * @twin.org/api-core bumped from 0.0.2-next.1 to 0.0.2-next.2 + * @twin.org/api-models bumped from 0.0.2-next.1 to 0.0.2-next.2 + +## [0.0.2-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.2-next.0...api-auth-entity-storage-rest-client-v0.0.2-next.1) (2025-07-08) + + +### Features + +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.0 to 0.0.2-next.1 + * @twin.org/api-core bumped from 0.0.2-next.0 to 0.0.2-next.1 + * @twin.org/api-models bumped from 0.0.2-next.0 to 0.0.2-next.1 + +## 0.0.1 (2025-07-03) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from ^0.0.0 to ^0.0.1 + * @twin.org/api-core bumped from ^0.0.0 to ^0.0.1 + * @twin.org/api-models bumped from ^0.0.0 to ^0.0.1 + +## [0.0.1-next.36](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.1-next.35...api-auth-entity-storage-rest-client-v0.0.1-next.36) (2025-06-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.35 to 0.0.1-next.36 + * @twin.org/api-core bumped from 0.0.1-next.35 to 0.0.1-next.36 + * @twin.org/api-models bumped from 0.0.1-next.35 to 0.0.1-next.36 + +## [0.0.1-next.35](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.1-next.34...api-auth-entity-storage-rest-client-v0.0.1-next.35) (2025-06-11) + + +### Features + +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.34 to 0.0.1-next.35 + * @twin.org/api-core bumped from 0.0.1-next.34 to 0.0.1-next.35 + * @twin.org/api-models bumped from 0.0.1-next.34 to 0.0.1-next.35 + +## [0.0.1-next.34](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.1-next.33...api-auth-entity-storage-rest-client-v0.0.1-next.34) (2025-05-27) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.33 to 0.0.1-next.34 + * @twin.org/api-core bumped from 0.0.1-next.33 to 0.0.1-next.34 + * @twin.org/api-models bumped from 0.0.1-next.33 to 0.0.1-next.34 + +## [0.0.1-next.33](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.1-next.32...api-auth-entity-storage-rest-client-v0.0.1-next.33) (2025-04-17) + + +### Features + +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.32 to 0.0.1-next.33 + * @twin.org/api-core bumped from 0.0.1-next.32 to 0.0.1-next.33 + * @twin.org/api-models bumped from 0.0.1-next.32 to 0.0.1-next.33 + +## [0.0.1-next.32](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-rest-client-v0.0.1-next.31...api-auth-entity-storage-rest-client-v0.0.1-next.32) (2025-03-28) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-rest-client:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.31 to 0.0.1-next.32 + * @twin.org/api-core bumped from 0.0.1-next.31 to 0.0.1-next.32 + * @twin.org/api-models bumped from 0.0.1-next.31 to 0.0.1-next.32 + +## v0.0.1-next.31 + +- Initial Release diff --git a/packages/api-auth-rest-client/docs/examples.md b/packages/api-auth-rest-client/docs/examples.md new file mode 100644 index 0000000..a3452b4 --- /dev/null +++ b/packages/api-auth-rest-client/docs/examples.md @@ -0,0 +1,62 @@ +# Auth Rest Client Examples + +Use these snippets to record and query authentication audit entries from browser or server-side TypeScript applications. + +## EntityStorageAuthenticationAuditRestClient + +```typescript +import { EntityStorageAuthenticationAuditRestClient } from '@3sixty/api-auth-rest-client'; + +const auditClient = new EntityStorageAuthenticationAuditRestClient({ + endpoint: 'https://api.example.org', + pathPrefix: 'v1' +}); + +console.log(auditClient.className()); // EntityStorageAuthenticationAuditRestClient + +const auditEntryId = await auditClient.create({ + event: 'login-success', + actorId: 'did:example:user:alice', + organizationId: 'did:example:org:core', + tenantId: 'did:example:tenant:alpha', + data: { + channel: 'web' + } +}); + +const auditResult = await auditClient.query( + { + actorId: 'did:example:user:alice', + event: 'login-success', + startDate: '2026-04-01T00:00:00.000Z', + endDate: '2026-04-30T23:59:59.999Z' + }, + undefined, + 25 +); + +console.log(auditEntryId); // /authentication/audit/018f2f67bb9d4a0caad8386f56df85ce +console.log(auditResult.entries.length); // 1 +console.log(auditResult.cursor); // eyJpZCI6IjAxOGYyZjY3YmI5ZDRhMGNhYWQ4Mzg2ZjU2ZGY4NWNlIn0= +``` + +```typescript +import { EntityStorageAuthenticationAuditRestClient } from '@3sixty/api-auth-rest-client'; + +const auditClient = new EntityStorageAuthenticationAuditRestClient({ + endpoint: 'https://api.example.org', + pathPrefix: 'v1' +}); + +const entry = await auditClient.get('018f2f67bb9d4a0caad8386f56df85ce'); + +await auditClient.update(entry.id, { + data: { + channel: 'mobile' + } +}); + +await auditClient.remove(entry.id); + +console.log(entry.event); // login-success +``` diff --git a/packages/api-auth-entity-storage-rest-client/docs/reference/classes/EntityStorageAuthenticationAuditRestClient.md b/packages/api-auth-rest-client/docs/reference/classes/EntityStorageAuthenticationAuditRestClient.md similarity index 100% rename from packages/api-auth-entity-storage-rest-client/docs/reference/classes/EntityStorageAuthenticationAuditRestClient.md rename to packages/api-auth-rest-client/docs/reference/classes/EntityStorageAuthenticationAuditRestClient.md diff --git a/packages/api-auth-rest-client/docs/reference/index.md b/packages/api-auth-rest-client/docs/reference/index.md new file mode 100644 index 0000000..a1d1d94 --- /dev/null +++ b/packages/api-auth-rest-client/docs/reference/index.md @@ -0,0 +1,5 @@ +# @twin.org/api-auth-rest-client + +## Classes + +- [EntityStorageAuthenticationAuditRestClient](classes/EntityStorageAuthenticationAuditRestClient.md) diff --git a/packages/api-auth-rest-client/locales/en.json b/packages/api-auth-rest-client/locales/en.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/packages/api-auth-rest-client/locales/en.json @@ -0,0 +1 @@ +{} diff --git a/packages/api-auth-rest-client/package.json b/packages/api-auth-rest-client/package.json new file mode 100644 index 0000000..2f55f49 --- /dev/null +++ b/packages/api-auth-rest-client/package.json @@ -0,0 +1,84 @@ +{ + "name": "@3sixty/api-auth-rest-client", + "version": "0.20.0-next.0", + "description": "REST client for the authentication audit endpoints.", + "repository": { + "type": "git", + "url": "git+https://github.com/3sixtyglobal/api-auth.git", + "directory": "packages/api-auth-rest-client" + }, + "author": "martyn.janes@iota.org", + "license": "Apache-2.0", + "type": "module", + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "clean": "rimraf dist coverage .cache", + "build": "tstransform", + "dev": "nodemon --watch src --ext ts --exec \"pnpm run build\"", + "test": "vitest --run --config ./vitest.config.ts", + "test:build": "tstransform -p ./tests/tsconfig.json", + "validate-locales": "validate-locales", + "test:coverage": "vitest --run --coverage --config ./vitest.config.ts", + "docs:clean": "rimraf docs/reference", + "docs:generate": "typedoc --options typedoc.config.mjs", + "docs": "pnpm run docs:clean && pnpm run docs:generate", + "dist": "pnpm run clean && concurrently -g -n build,locales,types,test \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\" \"pnpm run test\"", + "dist:no-test": "pnpm run clean && concurrently -g -n build,locales,types \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\"" + }, + "dependencies": { + "@3sixty/api-auth-models": "0.20.0-next.0", + "@3sixty/api-core": "next", + "@3sixty/api-models": "next", + "@3sixty/core": "next", + "@3sixty/nameof": "next", + "@3sixty/web": "next" + }, + "devDependencies": { + "@3sixty/nameof-transformer": "next", + "@3sixty/nameof-vitest-plugin": "next", + "@3sixty/validate-locales": "next", + "@types/node": "26.6.5", + "@vitest/coverage-v8": "5.0.5", + "concurrently": "10.0.6", + "copyfiles": "2.4.1", + "nodemon": "3.1.14", + "rimraf": "6.1.3", + "typedoc": "0.28.20", + "typedoc-plugin-markdown": "4.14.1", + "typescript": "6.0.3", + "vitest": "5.0.5" + }, + "main": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/es/index.js", + "default": "./dist/es/index.js" + } + }, + "files": [ + "dist/es", + "dist/types", + "locales", + "docs" + ], + "keywords": [ + "twin", + "trade", + "iota", + "framework", + "blockchain", + "api", + "entity", + "storage", + "persistence", + "database" + ], + "bugs": { + "url": "git+https://github.com/3sixtyglobal/api-auth/issues" + }, + "homepage": "https://3sixty.global" +} diff --git a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAuditRestClient.ts b/packages/api-auth-rest-client/src/entityStorageAuthenticationAuditRestClient.ts similarity index 99% rename from packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAuditRestClient.ts rename to packages/api-auth-rest-client/src/entityStorageAuthenticationAuditRestClient.ts index d1500f0..65d3923 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAuditRestClient.ts +++ b/packages/api-auth-rest-client/src/entityStorageAuthenticationAuditRestClient.ts @@ -11,7 +11,7 @@ import type { IAuditUpdateRequest, IAuthenticationAuditComponent, IAuthenticationAuditEntry -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { BaseRestClient } from "@3sixty/api-core"; import { HttpHeaderHelper } from "@3sixty/api-models"; import type { diff --git a/packages/api-auth-rest-client/src/index.ts b/packages/api-auth-rest-client/src/index.ts new file mode 100644 index 0000000..b8d3197 --- /dev/null +++ b/packages/api-auth-rest-client/src/index.ts @@ -0,0 +1,3 @@ +// Copyright 2024 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +export * from "./entityStorageAuthenticationAuditRestClient.js"; diff --git a/packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAuditRestClient.spec.ts b/packages/api-auth-rest-client/tests/entityStorageAuthenticationAuditRestClient.spec.ts similarity index 100% rename from packages/api-auth-entity-storage-rest-client/tests/entityStorageAuthenticationAuditRestClient.spec.ts rename to packages/api-auth-rest-client/tests/entityStorageAuthenticationAuditRestClient.spec.ts diff --git a/packages/api-auth-rest-client/tests/helpers/restClientTestHelpers.ts b/packages/api-auth-rest-client/tests/helpers/restClientTestHelpers.ts new file mode 100644 index 0000000..71b0eaa --- /dev/null +++ b/packages/api-auth-rest-client/tests/helpers/restClientTestHelpers.ts @@ -0,0 +1,93 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { HeaderTypes, HttpStatusCode, MimeTypes } from "@3sixty/web"; + +/** + * Minimal 204 No-Content response accepted by BaseRestClient. + * Use for operations that return void. + * @returns A fake 204 response object. + */ +export function noContentResponse(): object { + return { + ok: true, + status: HttpStatusCode.noContent, + headers: new Headers() + }; +} + +/** + * Minimal 200 OK JSON response accepted by BaseRestClient. + * @param jsonBody The value returned by response.json() - this becomes response.body in the client. + * @returns A fake 200 JSON response object. + */ +export function jsonResponse(jsonBody: unknown): object { + return { + ok: true, + status: HttpStatusCode.ok, + headers: new Headers({ [HeaderTypes.ContentType]: MimeTypes.Json }), + json: async () => jsonBody + }; +} + +/** + * Minimal 200 OK plain-text response accepted by BaseRestClient. + * @param text The value returned by response.text() - this becomes response.body in the client. + * @returns A fake 200 plain-text response object. + */ +export function textResponse(text: string): object { + return { + ok: true, + status: HttpStatusCode.ok, + headers: new Headers({ [HeaderTypes.ContentType]: MimeTypes.PlainText }), + text: async () => text + }; +} + +/** + * Minimal 200 OK binary response accepted by BaseRestClient. + * Body is stored as Uint8Array; an empty buffer causes body to be undefined. + * @param data The bytes to return via response.arrayBuffer(). + * @returns A fake 200 binary response object. + */ +export function binaryResponse(data: Uint8Array): object { + return { + ok: true, + status: HttpStatusCode.ok, + headers: new Headers(), + arrayBuffer: async () => data.buffer as ArrayBuffer + }; +} + +/** + * Minimal 201 Created response accepted by BaseRestClient. + * The location is surfaced as response.headers[HeaderTypes.Location] in the client. + * @param location The value to set in the Location header. + * @returns A fake 201 Created response object. + */ +export function createdResponse(location: string): object { + return { + ok: true, + status: HttpStatusCode.created, + headers: new Headers({ [HeaderTypes.Location]: location }), + arrayBuffer: async () => new ArrayBuffer(0) + }; +} + +/** + * Assigns the given mock as the global fetch implementation. + * Call in beforeEach alongside any client construction. + * @param mock The vi.fn() mock to install as globalThis.fetch. + */ +export function setupFetchMock(mock: typeof fetch): void { + globalThis.fetch = mock; +} + +/** + * Resets the mock and removes it as the global fetch. + * Call in afterEach to prevent state leaking between tests. + * @param mock The same mock passed to setupFetchMock. + * @param mock.mockReset Vitest method that clears call history and return values. + */ +export function teardownFetchMock(mock: { mockReset(): void }): void { + mock.mockReset(); +} diff --git a/packages/api-auth-rest-client/tests/index.spec.ts b/packages/api-auth-rest-client/tests/index.spec.ts new file mode 100644 index 0000000..e2d623a --- /dev/null +++ b/packages/api-auth-rest-client/tests/index.spec.ts @@ -0,0 +1,6 @@ +// Copyright 2024 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +describe("api-auth-entity-storage-rest-client", () => { + test("This package currently has no tests", () => {}); +}); diff --git a/packages/api-auth-rest-client/tests/tsconfig.json b/packages/api-auth-rest-client/tests/tsconfig.json new file mode 100644 index 0000000..8f550b2 --- /dev/null +++ b/packages/api-auth-rest-client/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "../.cache/tests.tsbuildinfo", + "rootDir": "..", + "types": ["vitest/globals"] + }, + "include": ["./**/*.ts"] +} diff --git a/packages/api-auth-rest-client/tsconfig.json b/packages/api-auth-rest-client/tsconfig.json new file mode 100644 index 0000000..a8a4cec --- /dev/null +++ b/packages/api-auth-rest-client/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo", + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "ES2022", + "rootDir": "./src", + "outDir": "dist/es", + "strict": true, + "forceConsistentCasingInFileNames": true, + "stripInternal": true, + "noEmitHelpers": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "erasableSyntaxOnly": true, + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "declarationDir": "dist/types", + "types": ["node"], + "plugins": [{ "transform": "@3sixty/nameof-transformer" }], + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/api-auth-rest-client/typedoc.config.mjs b/packages/api-auth-rest-client/typedoc.config.mjs new file mode 100644 index 0000000..aad5b59 --- /dev/null +++ b/packages/api-auth-rest-client/typedoc.config.mjs @@ -0,0 +1,24 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { OptionDefaults } from 'typedoc'; + +const customSchemaTags = ['@json-ld', '@json-schema']; + +export default { + entryPoints: ['src/index.ts'], + out: './docs/reference', + hideGenerator: true, + hideBreadcrumbs: true, + hidePageHeader: true, + excludeExternals: true, + githubPages: false, + disableSources: true, + sort: ['source-order'], + treatWarningsAsErrors: true, + readme: 'none', + entryFileName: 'index', + plugin: ['typedoc-plugin-markdown'], + blockTags: [...new Set([...OptionDefaults.blockTags, ...customSchemaTags])], + excludeTags: customSchemaTags, + useCustomAnchors: true +}; diff --git a/packages/api-auth-rest-client/vitest.config.ts b/packages/api-auth-rest-client/vitest.config.ts new file mode 100644 index 0000000..d40698f --- /dev/null +++ b/packages/api-auth-rest-client/vitest.config.ts @@ -0,0 +1,24 @@ +// Copyright 2025 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { NameOfPlugin } from "@3sixty/nameof-vitest-plugin"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [NameOfPlugin], + test: { + include: ["./tests/**/*.spec.ts"], + globals: true, + cache: false, + testTimeout: 300000, + hookTimeout: 300000, + bail: 1, + reporters: ["verbose"], + disableConsoleIntercept: true, + coverage: { + reporter: ["text", "lcov"], + include: ["src/**/*.ts"], + exclude: ["**/index.ts", "**/models/**/*.ts"] + }, + fileParallelism: false + } +}); diff --git a/packages/api-auth-service/LICENSE b/packages/api-auth-service/LICENSE new file mode 100644 index 0000000..677c34c --- /dev/null +++ b/packages/api-auth-service/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 IOTA Stiftung. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/api-auth-service/README.md b/packages/api-auth-service/README.md new file mode 100644 index 0000000..4c3a46a --- /dev/null +++ b/packages/api-auth-service/README.md @@ -0,0 +1,25 @@ +# 3Sixty API Auth Service + +This package provides authentication audit and rate limiting services, tenant override processing and audit REST routes backed by entity storage. + +## Installation + +```shell +npm install @3sixty/api-auth-service +``` + +## Examples + +Usage of the APIs is shown in the examples [docs/examples.md](docs/examples.md) + +## Reference + +Detailed reference documentation for the API can be found in [docs/reference/index.md](docs/reference/index.md) + +## Changelog + +The changes between each version can be found in [docs/changelog.md](docs/changelog.md) + +## Origin + +This package is derived from the original [iotaledger/twin-api-auth](https://github.com/iotaledger/twin-api-auth/tree/feature/authorization/packages/api-auth-service) repository. diff --git a/packages/api-auth-service/docs/changelog.md b/packages/api-auth-service/docs/changelog.md new file mode 100644 index 0000000..100b7da --- /dev/null +++ b/packages/api-auth-service/docs/changelog.md @@ -0,0 +1,1619 @@ +# Changelog + +## [0.9.2-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.6...api-auth-entity-storage-service-v0.9.2-next.7) (2026-08-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.6 to 0.9.2-next.7 + * @twin.org/api-core bumped from 0.9.2-next.6 to 0.9.2-next.7 + * @twin.org/api-models bumped from 0.9.2-next.6 to 0.9.2-next.7 + +## [0.9.2-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.5...api-auth-entity-storage-service-v0.9.2-next.6) (2026-08-07) + + +### Features + +* health updates ([#243](https://github.com/iotaledger/twin-api/issues/243)) ([8360945](https://github.com/iotaledger/twin-api/commit/836094547839f63f2309d4bcd8c4d19128c73883)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.5 to 0.9.2-next.6 + * @twin.org/api-core bumped from 0.9.2-next.5 to 0.9.2-next.6 + * @twin.org/api-models bumped from 0.9.2-next.5 to 0.9.2-next.6 + +## [0.9.2-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.4...api-auth-entity-storage-service-v0.9.2-next.5) (2026-08-03) + + +### Features + +* health application checks ([#240](https://github.com/iotaledger/twin-api/issues/240)) ([68ec8b2](https://github.com/iotaledger/twin-api/commit/68ec8b29dae396928aa874826565efa6ad5d18c9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.4 to 0.9.2-next.5 + * @twin.org/api-core bumped from 0.9.2-next.4 to 0.9.2-next.5 + * @twin.org/api-models bumped from 0.9.2-next.4 to 0.9.2-next.5 + +## [0.9.2-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.3...api-auth-entity-storage-service-v0.9.2-next.4) (2026-08-02) + + +### Bug Fixes + +* preserve error stack in error responses when includeErrorStack is enabled ([#238](https://github.com/iotaledger/twin-api/issues/238)) ([7abc9b7](https://github.com/iotaledger/twin-api/commit/7abc9b79d07724abb68b2c06467cbdb54c880083)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.3 to 0.9.2-next.4 + * @twin.org/api-core bumped from 0.9.2-next.3 to 0.9.2-next.4 + * @twin.org/api-models bumped from 0.9.2-next.3 to 0.9.2-next.4 + +## [0.9.2-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.2...api-auth-entity-storage-service-v0.9.2-next.3) (2026-07-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.2 to 0.9.2-next.3 + * @twin.org/api-core bumped from 0.9.2-next.2 to 0.9.2-next.3 + * @twin.org/api-models bumped from 0.9.2-next.2 to 0.9.2-next.3 + +## [0.9.2-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.1...api-auth-entity-storage-service-v0.9.2-next.2) (2026-07-29) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.1 to 0.9.2-next.2 + * @twin.org/api-core bumped from 0.9.2-next.1 to 0.9.2-next.2 + * @twin.org/api-models bumped from 0.9.2-next.1 to 0.9.2-next.2 + +## [0.9.2-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.2-next.0...api-auth-entity-storage-service-v0.9.2-next.1) (2026-07-28) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add livez endpoint ([#57](https://github.com/iotaledger/twin-api/issues/57)) ([ef007db](https://github.com/iotaledger/twin-api/commit/ef007db8201736dd3053211f849ffd03baaa485e)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* auth header processor use user entity storage directly ([93084cf](https://github.com/iotaledger/twin-api/commit/93084cfed8f3ab16c80a1ab1bc90b1d9fc14bd25)) +* check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7)) +* enhanced rest handling ([#208](https://github.com/iotaledger/twin-api/issues/208)) ([99d5f3f](https://github.com/iotaledger/twin-api/commit/99d5f3f96d262e57828d98d3f3b1e3da8a863378)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* hashed tenant id ([#135](https://github.com/iotaledger/twin-api/issues/135)) ([573c9b0](https://github.com/iotaledger/twin-api/commit/573c9b0714c281d69d5626c7576e93db826d266d)) +* improved rest handling ([#211](https://github.com/iotaledger/twin-api/issues/211)) ([12c50ea](https://github.com/iotaledger/twin-api/commit/12c50ea7655276eed7a73d311ac5286476c98201)) +* include iss and kid in jwt ([#189](https://github.com/iotaledger/twin-api/issues/189)) ([9df072a](https://github.com/iotaledger/twin-api/commit/9df072a1ecfc60dec611f59001449089264bbb1c)) +* jwt password version claim ([#129](https://github.com/iotaledger/twin-api/issues/129)) ([e15487b](https://github.com/iotaledger/twin-api/commit/e15487baafd3b1f3fd58f138db531d58053ce049)) +* location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* organization identifiers ([#158](https://github.com/iotaledger/twin-api/issues/158)) ([ce13244](https://github.com/iotaledger/twin-api/commit/ce13244aaacbf82d9e5f87d905e283b36ad63bbf)) +* remove auth service dependency on admin service ([4aeb211](https://github.com/iotaledger/twin-api/commit/4aeb211a5ffcf2eaea89a6841faea3ac3069ef89)) +* remove hosting component ([#170](https://github.com/iotaledger/twin-api/issues/170)) ([e78c1e8](https://github.com/iotaledger/twin-api/commit/e78c1e87d2747bf58da02b6b77680708ff681122)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083)) +* tenant id in jwt ([#140](https://github.com/iotaledger/twin-api/issues/140)) ([8d37a7b](https://github.com/iotaledger/twin-api/commit/8d37a7b98b45fde53df1e909cffc0869aa758655)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* handling the tid as undefined at refresh when empty ([#104](https://github.com/iotaledger/twin-api/issues/104)) ([d874a4a](https://github.com/iotaledger/twin-api/commit/d874a4a542c5fd45bb1bd56650247f844d056643)) +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) +* remove token from return payload ([eaa4266](https://github.com/iotaledger/twin-api/commit/eaa42661b8540881b0751f5d2513258b3413f3ef)) +* run authentication rate cleanup per tenant ([#186](https://github.com/iotaledger/twin-api/issues/186)) ([d69c7aa](https://github.com/iotaledger/twin-api/commit/d69c7aa4ade77f1a52f1f554d8f7a3bd20f1b330)) +* use async getStore in tests ([0d25633](https://github.com/iotaledger/twin-api/commit/0d2563364de06a54ba487c700b601a883402d292)) +* use async getStore in tests ([63493dd](https://github.com/iotaledger/twin-api/commit/63493ddf3cf394c0d1e4cfcddae9a39f7ac697c4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.2-next.0 to 0.9.2-next.1 + * @twin.org/api-core bumped from 0.9.2-next.0 to 0.9.2-next.1 + * @twin.org/api-models bumped from 0.9.2-next.0 to 0.9.2-next.1 + +## [0.9.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1...api-auth-entity-storage-service-v0.9.1) (2026-07-27) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) +* release to production ([#195](https://github.com/iotaledger/twin-api/issues/195)) ([a3f5c1f](https://github.com/iotaledger/twin-api/commit/a3f5c1fc35a748762af7efa4f7f95776004d1309)) +* release to production ([#197](https://github.com/iotaledger/twin-api/issues/197)) ([f04c156](https://github.com/iotaledger/twin-api/commit/f04c1567f801cde36c5ec8595f9b9369109d9e42)) +* release to production ([#201](https://github.com/iotaledger/twin-api/issues/201)) ([e1c46fd](https://github.com/iotaledger/twin-api/commit/e1c46fd02c1f4d44d5393e2f49a24f1e4468f240)) +* release to production ([#224](https://github.com/iotaledger/twin-api/issues/224)) ([dffaf08](https://github.com/iotaledger/twin-api/commit/dffaf082b7dccc6f57a5b7cd20b95d24bb8ec2f3)) + +## [0.9.1-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.8...api-auth-entity-storage-service-v0.9.1-next.9) (2026-07-23) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.8 to 0.9.1-next.9 + * @twin.org/api-core bumped from 0.9.1-next.8 to 0.9.1-next.9 + * @twin.org/api-models bumped from 0.9.1-next.8 to 0.9.1-next.9 + +## [0.9.1-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.7...api-auth-entity-storage-service-v0.9.1-next.8) (2026-07-03) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.7 to 0.9.1-next.8 + * @twin.org/api-core bumped from 0.9.1-next.7 to 0.9.1-next.8 + * @twin.org/api-models bumped from 0.9.1-next.7 to 0.9.1-next.8 + +## [0.9.1-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.6...api-auth-entity-storage-service-v0.9.1-next.7) (2026-07-02) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.6 to 0.9.1-next.7 + * @twin.org/api-core bumped from 0.9.1-next.6 to 0.9.1-next.7 + * @twin.org/api-models bumped from 0.9.1-next.6 to 0.9.1-next.7 + +## [0.9.1-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.5...api-auth-entity-storage-service-v0.9.1-next.6) (2026-06-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.5 to 0.9.1-next.6 + * @twin.org/api-core bumped from 0.9.1-next.5 to 0.9.1-next.6 + * @twin.org/api-models bumped from 0.9.1-next.5 to 0.9.1-next.6 + +## [0.9.1-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.4...api-auth-entity-storage-service-v0.9.1-next.5) (2026-06-30) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.4 to 0.9.1-next.5 + * @twin.org/api-core bumped from 0.9.1-next.4 to 0.9.1-next.5 + * @twin.org/api-models bumped from 0.9.1-next.4 to 0.9.1-next.5 + +## [0.9.1-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.3...api-auth-entity-storage-service-v0.9.1-next.4) (2026-06-30) + + +### Features + +* improved rest handling ([#211](https://github.com/iotaledger/twin-api/issues/211)) ([12c50ea](https://github.com/iotaledger/twin-api/commit/12c50ea7655276eed7a73d311ac5286476c98201)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.3 to 0.9.1-next.4 + * @twin.org/api-core bumped from 0.9.1-next.3 to 0.9.1-next.4 + * @twin.org/api-models bumped from 0.9.1-next.3 to 0.9.1-next.4 + +## [0.9.1-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.2...api-auth-entity-storage-service-v0.9.1-next.3) (2026-06-29) + + +### Features + +* enhanced rest handling ([#208](https://github.com/iotaledger/twin-api/issues/208)) ([99d5f3f](https://github.com/iotaledger/twin-api/commit/99d5f3f96d262e57828d98d3f3b1e3da8a863378)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.2 to 0.9.1-next.3 + * @twin.org/api-core bumped from 0.9.1-next.2 to 0.9.1-next.3 + * @twin.org/api-models bumped from 0.9.1-next.2 to 0.9.1-next.3 + +## [0.9.1-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.1...api-auth-entity-storage-service-v0.9.1-next.2) (2026-06-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.1 to 0.9.1-next.2 + * @twin.org/api-core bumped from 0.9.1-next.1 to 0.9.1-next.2 + * @twin.org/api-models bumped from 0.9.1-next.1 to 0.9.1-next.2 + +## [0.9.1-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.1-next.0...api-auth-entity-storage-service-v0.9.1-next.1) (2026-06-26) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add livez endpoint ([#57](https://github.com/iotaledger/twin-api/issues/57)) ([ef007db](https://github.com/iotaledger/twin-api/commit/ef007db8201736dd3053211f849ffd03baaa485e)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* auth header processor use user entity storage directly ([93084cf](https://github.com/iotaledger/twin-api/commit/93084cfed8f3ab16c80a1ab1bc90b1d9fc14bd25)) +* check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* hashed tenant id ([#135](https://github.com/iotaledger/twin-api/issues/135)) ([573c9b0](https://github.com/iotaledger/twin-api/commit/573c9b0714c281d69d5626c7576e93db826d266d)) +* include iss and kid in jwt ([#189](https://github.com/iotaledger/twin-api/issues/189)) ([9df072a](https://github.com/iotaledger/twin-api/commit/9df072a1ecfc60dec611f59001449089264bbb1c)) +* jwt password version claim ([#129](https://github.com/iotaledger/twin-api/issues/129)) ([e15487b](https://github.com/iotaledger/twin-api/commit/e15487baafd3b1f3fd58f138db531d58053ce049)) +* location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* organization identifiers ([#158](https://github.com/iotaledger/twin-api/issues/158)) ([ce13244](https://github.com/iotaledger/twin-api/commit/ce13244aaacbf82d9e5f87d905e283b36ad63bbf)) +* remove auth service dependency on admin service ([4aeb211](https://github.com/iotaledger/twin-api/commit/4aeb211a5ffcf2eaea89a6841faea3ac3069ef89)) +* remove hosting component ([#170](https://github.com/iotaledger/twin-api/issues/170)) ([e78c1e8](https://github.com/iotaledger/twin-api/commit/e78c1e87d2747bf58da02b6b77680708ff681122)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083)) +* tenant id in jwt ([#140](https://github.com/iotaledger/twin-api/issues/140)) ([8d37a7b](https://github.com/iotaledger/twin-api/commit/8d37a7b98b45fde53df1e909cffc0869aa758655)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* handling the tid as undefined at refresh when empty ([#104](https://github.com/iotaledger/twin-api/issues/104)) ([d874a4a](https://github.com/iotaledger/twin-api/commit/d874a4a542c5fd45bb1bd56650247f844d056643)) +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) +* remove token from return payload ([eaa4266](https://github.com/iotaledger/twin-api/commit/eaa42661b8540881b0751f5d2513258b3413f3ef)) +* run authentication rate cleanup per tenant ([#186](https://github.com/iotaledger/twin-api/issues/186)) ([d69c7aa](https://github.com/iotaledger/twin-api/commit/d69c7aa4ade77f1a52f1f554d8f7a3bd20f1b330)) +* use async getStore in tests ([0d25633](https://github.com/iotaledger/twin-api/commit/0d2563364de06a54ba487c700b601a883402d292)) +* use async getStore in tests ([63493dd](https://github.com/iotaledger/twin-api/commit/63493ddf3cf394c0d1e4cfcddae9a39f7ac697c4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.1-next.0 to 0.9.1-next.1 + * @twin.org/api-core bumped from 0.9.1-next.0 to 0.9.1-next.1 + * @twin.org/api-models bumped from 0.9.1-next.0 to 0.9.1-next.1 + +## [0.9.0](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.0...api-auth-entity-storage-service-v0.9.0) (2026-06-24) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) +* release to production ([#195](https://github.com/iotaledger/twin-api/issues/195)) ([a3f5c1f](https://github.com/iotaledger/twin-api/commit/a3f5c1fc35a748762af7efa4f7f95776004d1309)) +* release to production ([#197](https://github.com/iotaledger/twin-api/issues/197)) ([f04c156](https://github.com/iotaledger/twin-api/commit/f04c1567f801cde36c5ec8595f9b9369109d9e42)) +* release to production ([#201](https://github.com/iotaledger/twin-api/issues/201)) ([e1c46fd](https://github.com/iotaledger/twin-api/commit/e1c46fd02c1f4d44d5393e2f49a24f1e4468f240)) + +## [0.9.0-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.9.0-next.0...api-auth-entity-storage-service-v0.9.0-next.1) (2026-06-23) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add livez endpoint ([#57](https://github.com/iotaledger/twin-api/issues/57)) ([ef007db](https://github.com/iotaledger/twin-api/commit/ef007db8201736dd3053211f849ffd03baaa485e)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) +* auth header processor use user entity storage directly ([93084cf](https://github.com/iotaledger/twin-api/commit/93084cfed8f3ab16c80a1ab1bc90b1d9fc14bd25)) +* check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* hashed tenant id ([#135](https://github.com/iotaledger/twin-api/issues/135)) ([573c9b0](https://github.com/iotaledger/twin-api/commit/573c9b0714c281d69d5626c7576e93db826d266d)) +* include iss and kid in jwt ([#189](https://github.com/iotaledger/twin-api/issues/189)) ([9df072a](https://github.com/iotaledger/twin-api/commit/9df072a1ecfc60dec611f59001449089264bbb1c)) +* jwt password version claim ([#129](https://github.com/iotaledger/twin-api/issues/129)) ([e15487b](https://github.com/iotaledger/twin-api/commit/e15487baafd3b1f3fd58f138db531d58053ce049)) +* location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* organization identifiers ([#158](https://github.com/iotaledger/twin-api/issues/158)) ([ce13244](https://github.com/iotaledger/twin-api/commit/ce13244aaacbf82d9e5f87d905e283b36ad63bbf)) +* remove auth service dependency on admin service ([4aeb211](https://github.com/iotaledger/twin-api/commit/4aeb211a5ffcf2eaea89a6841faea3ac3069ef89)) +* remove hosting component ([#170](https://github.com/iotaledger/twin-api/issues/170)) ([e78c1e8](https://github.com/iotaledger/twin-api/commit/e78c1e87d2747bf58da02b6b77680708ff681122)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083)) +* tenant id in jwt ([#140](https://github.com/iotaledger/twin-api/issues/140)) ([8d37a7b](https://github.com/iotaledger/twin-api/commit/8d37a7b98b45fde53df1e909cffc0869aa758655)) +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) +* handling the tid as undefined at refresh when empty ([#104](https://github.com/iotaledger/twin-api/issues/104)) ([d874a4a](https://github.com/iotaledger/twin-api/commit/d874a4a542c5fd45bb1bd56650247f844d056643)) +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) +* remove token from return payload ([eaa4266](https://github.com/iotaledger/twin-api/commit/eaa42661b8540881b0751f5d2513258b3413f3ef)) +* run authentication rate cleanup per tenant ([#186](https://github.com/iotaledger/twin-api/issues/186)) ([d69c7aa](https://github.com/iotaledger/twin-api/commit/d69c7aa4ade77f1a52f1f554d8f7a3bd20f1b330)) +* use async getStore in tests ([0d25633](https://github.com/iotaledger/twin-api/commit/0d2563364de06a54ba487c700b601a883402d292)) +* use async getStore in tests ([63493dd](https://github.com/iotaledger/twin-api/commit/63493ddf3cf394c0d1e4cfcddae9a39f7ac697c4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.9.0-next.0 to 0.9.0-next.1 + * @twin.org/api-core bumped from 0.9.0-next.0 to 0.9.0-next.1 + * @twin.org/api-models bumped from 0.9.0-next.0 to 0.9.0-next.1 + +## [0.0.3-next.53](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.52...api-auth-entity-storage-service-v0.0.3-next.53) (2026-06-23) + + +### Features + +* include iss and kid in jwt ([#189](https://github.com/iotaledger/twin-api/issues/189)) ([9df072a](https://github.com/iotaledger/twin-api/commit/9df072a1ecfc60dec611f59001449089264bbb1c)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.52 to 0.0.3-next.53 + * @twin.org/api-core bumped from 0.0.3-next.52 to 0.0.3-next.53 + * @twin.org/api-models bumped from 0.0.3-next.52 to 0.0.3-next.53 + +## [0.0.3-next.52](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.51...api-auth-entity-storage-service-v0.0.3-next.52) (2026-06-22) + + +### Bug Fixes + +* run authentication rate cleanup per tenant ([#186](https://github.com/iotaledger/twin-api/issues/186)) ([d69c7aa](https://github.com/iotaledger/twin-api/commit/d69c7aa4ade77f1a52f1f554d8f7a3bd20f1b330)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.51 to 0.0.3-next.52 + * @twin.org/api-core bumped from 0.0.3-next.51 to 0.0.3-next.52 + * @twin.org/api-models bumped from 0.0.3-next.51 to 0.0.3-next.52 + +## [0.0.3-next.51](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.50...api-auth-entity-storage-service-v0.0.3-next.51) (2026-06-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.50 to 0.0.3-next.51 + * @twin.org/api-core bumped from 0.0.3-next.50 to 0.0.3-next.51 + * @twin.org/api-models bumped from 0.0.3-next.50 to 0.0.3-next.51 + +## [0.0.3-next.50](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.49...api-auth-entity-storage-service-v0.0.3-next.50) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.49 to 0.0.3-next.50 + * @twin.org/api-core bumped from 0.0.3-next.49 to 0.0.3-next.50 + * @twin.org/api-models bumped from 0.0.3-next.49 to 0.0.3-next.50 + +## [0.0.3-next.49](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.48...api-auth-entity-storage-service-v0.0.3-next.49) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.48 to 0.0.3-next.49 + * @twin.org/api-core bumped from 0.0.3-next.48 to 0.0.3-next.49 + * @twin.org/api-models bumped from 0.0.3-next.48 to 0.0.3-next.49 + +## [0.0.3-next.48](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.47...api-auth-entity-storage-service-v0.0.3-next.48) (2026-06-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.47 to 0.0.3-next.48 + * @twin.org/api-core bumped from 0.0.3-next.47 to 0.0.3-next.48 + * @twin.org/api-models bumped from 0.0.3-next.47 to 0.0.3-next.48 + +## [0.0.3-next.47](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.46...api-auth-entity-storage-service-v0.0.3-next.47) (2026-06-18) + + +### Features + +* remove hosting component ([#170](https://github.com/iotaledger/twin-api/issues/170)) ([e78c1e8](https://github.com/iotaledger/twin-api/commit/e78c1e87d2747bf58da02b6b77680708ff681122)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.46 to 0.0.3-next.47 + * @twin.org/api-core bumped from 0.0.3-next.46 to 0.0.3-next.47 + * @twin.org/api-models bumped from 0.0.3-next.46 to 0.0.3-next.47 + +## [0.0.3-next.46](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.45...api-auth-entity-storage-service-v0.0.3-next.46) (2026-06-17) + + +### Bug Fixes + +* use async getStore in tests ([0d25633](https://github.com/iotaledger/twin-api/commit/0d2563364de06a54ba487c700b601a883402d292)) +* use async getStore in tests ([63493dd](https://github.com/iotaledger/twin-api/commit/63493ddf3cf394c0d1e4cfcddae9a39f7ac697c4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.45 to 0.0.3-next.46 + * @twin.org/api-core bumped from 0.0.3-next.45 to 0.0.3-next.46 + * @twin.org/api-models bumped from 0.0.3-next.45 to 0.0.3-next.46 + +## [0.0.3-next.45](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.44...api-auth-entity-storage-service-v0.0.3-next.45) (2026-06-15) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.44 to 0.0.3-next.45 + * @twin.org/api-core bumped from 0.0.3-next.44 to 0.0.3-next.45 + * @twin.org/api-models bumped from 0.0.3-next.44 to 0.0.3-next.45 + +## [0.0.3-next.44](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.43...api-auth-entity-storage-service-v0.0.3-next.44) (2026-06-11) + + +### Features + +* organization identifiers ([#158](https://github.com/iotaledger/twin-api/issues/158)) ([ce13244](https://github.com/iotaledger/twin-api/commit/ce13244aaacbf82d9e5f87d905e283b36ad63bbf)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.43 to 0.0.3-next.44 + * @twin.org/api-core bumped from 0.0.3-next.43 to 0.0.3-next.44 + * @twin.org/api-models bumped from 0.0.3-next.43 to 0.0.3-next.44 + +## [0.0.3-next.43](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.42...api-auth-entity-storage-service-v0.0.3-next.43) (2026-06-10) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.42 to 0.0.3-next.43 + * @twin.org/api-core bumped from 0.0.3-next.42 to 0.0.3-next.43 + * @twin.org/api-models bumped from 0.0.3-next.42 to 0.0.3-next.43 + +## [0.0.3-next.42](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.41...api-auth-entity-storage-service-v0.0.3-next.42) (2026-06-08) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.41 to 0.0.3-next.42 + * @twin.org/api-core bumped from 0.0.3-next.41 to 0.0.3-next.42 + * @twin.org/api-models bumped from 0.0.3-next.41 to 0.0.3-next.42 + +## [0.0.3-next.41](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.40...api-auth-entity-storage-service-v0.0.3-next.41) (2026-06-05) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.40 to 0.0.3-next.41 + * @twin.org/api-core bumped from 0.0.3-next.40 to 0.0.3-next.41 + * @twin.org/api-models bumped from 0.0.3-next.40 to 0.0.3-next.41 + +## [0.0.3-next.40](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.39...api-auth-entity-storage-service-v0.0.3-next.40) (2026-06-04) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.39 to 0.0.3-next.40 + * @twin.org/api-core bumped from 0.0.3-next.39 to 0.0.3-next.40 + * @twin.org/api-models bumped from 0.0.3-next.39 to 0.0.3-next.40 + +## [0.0.3-next.39](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.38...api-auth-entity-storage-service-v0.0.3-next.39) (2026-06-02) + + +### Features + +* tenant id in jwt ([#140](https://github.com/iotaledger/twin-api/issues/140)) ([8d37a7b](https://github.com/iotaledger/twin-api/commit/8d37a7b98b45fde53df1e909cffc0869aa758655)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.38 to 0.0.3-next.39 + * @twin.org/api-core bumped from 0.0.3-next.38 to 0.0.3-next.39 + * @twin.org/api-models bumped from 0.0.3-next.38 to 0.0.3-next.39 + +## [0.0.3-next.38](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.37...api-auth-entity-storage-service-v0.0.3-next.38) (2026-05-28) + + +### Features + +* hashed tenant id ([#135](https://github.com/iotaledger/twin-api/issues/135)) ([573c9b0](https://github.com/iotaledger/twin-api/commit/573c9b0714c281d69d5626c7576e93db826d266d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.37 to 0.0.3-next.38 + * @twin.org/api-core bumped from 0.0.3-next.37 to 0.0.3-next.38 + * @twin.org/api-models bumped from 0.0.3-next.37 to 0.0.3-next.38 + +## [0.0.3-next.37](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.36...api-auth-entity-storage-service-v0.0.3-next.37) (2026-05-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.36 to 0.0.3-next.37 + * @twin.org/api-core bumped from 0.0.3-next.36 to 0.0.3-next.37 + * @twin.org/api-models bumped from 0.0.3-next.36 to 0.0.3-next.37 + +## [0.0.3-next.36](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.35...api-auth-entity-storage-service-v0.0.3-next.36) (2026-05-22) + + +### Features + +* jwt password version claim ([#129](https://github.com/iotaledger/twin-api/issues/129)) ([e15487b](https://github.com/iotaledger/twin-api/commit/e15487baafd3b1f3fd58f138db531d58053ce049)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.35 to 0.0.3-next.36 + * @twin.org/api-core bumped from 0.0.3-next.35 to 0.0.3-next.36 + * @twin.org/api-models bumped from 0.0.3-next.35 to 0.0.3-next.36 + +## [0.0.3-next.35](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.34...api-auth-entity-storage-service-v0.0.3-next.35) (2026-05-21) + + +### Features + +* user partitioning ([#126](https://github.com/iotaledger/twin-api/issues/126)) ([6bf0da3](https://github.com/iotaledger/twin-api/commit/6bf0da3c42406c9838e80e0ddd6b21f5c64aac90)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.34 to 0.0.3-next.35 + * @twin.org/api-core bumped from 0.0.3-next.34 to 0.0.3-next.35 + * @twin.org/api-models bumped from 0.0.3-next.34 to 0.0.3-next.35 + +## [0.0.3-next.34](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.33...api-auth-entity-storage-service-v0.0.3-next.34) (2026-05-19) + + +### Features + +* update dependencies ([32b8cd2](https://github.com/iotaledger/twin-api/commit/32b8cd20353119dd1998e293d54063cf4d9ecc29)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.33 to 0.0.3-next.34 + * @twin.org/api-core bumped from 0.0.3-next.33 to 0.0.3-next.34 + * @twin.org/api-models bumped from 0.0.3-next.33 to 0.0.3-next.34 + +## [0.0.3-next.33](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.32...api-auth-entity-storage-service-v0.0.3-next.33) (2026-05-11) + + +### Features + +* typescript 6 update ([78d2aa0](https://github.com/iotaledger/twin-api/commit/78d2aa00902f79b61973079b798b87ec05f18a8b)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.32 to 0.0.3-next.33 + * @twin.org/api-core bumped from 0.0.3-next.32 to 0.0.3-next.33 + * @twin.org/api-models bumped from 0.0.3-next.32 to 0.0.3-next.33 + +## [0.0.3-next.32](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.31...api-auth-entity-storage-service-v0.0.3-next.32) (2026-05-07) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.31 to 0.0.3-next.32 + * @twin.org/api-core bumped from 0.0.3-next.31 to 0.0.3-next.32 + * @twin.org/api-models bumped from 0.0.3-next.31 to 0.0.3-next.32 + +## [0.0.3-next.31](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.30...api-auth-entity-storage-service-v0.0.3-next.31) (2026-05-06) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.30 to 0.0.3-next.31 + * @twin.org/api-core bumped from 0.0.3-next.30 to 0.0.3-next.31 + * @twin.org/api-models bumped from 0.0.3-next.30 to 0.0.3-next.31 + +## [0.0.3-next.30](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.29...api-auth-entity-storage-service-v0.0.3-next.30) (2026-05-05) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.29 to 0.0.3-next.30 + * @twin.org/api-core bumped from 0.0.3-next.29 to 0.0.3-next.30 + * @twin.org/api-models bumped from 0.0.3-next.29 to 0.0.3-next.30 + +## [0.0.3-next.29](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.28...api-auth-entity-storage-service-v0.0.3-next.29) (2026-05-01) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.28 to 0.0.3-next.29 + * @twin.org/api-core bumped from 0.0.3-next.28 to 0.0.3-next.29 + * @twin.org/api-models bumped from 0.0.3-next.28 to 0.0.3-next.29 + +## [0.0.3-next.28](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.27...api-auth-entity-storage-service-v0.0.3-next.28) (2026-04-30) + + +### Bug Fixes + +* change logout and refresh routes from GET to POST ([#111](https://github.com/iotaledger/twin-api/issues/111)) ([cb8b64b](https://github.com/iotaledger/twin-api/commit/cb8b64b6507f9991baa78a663de2e84269695c82)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.27 to 0.0.3-next.28 + * @twin.org/api-core bumped from 0.0.3-next.27 to 0.0.3-next.28 + * @twin.org/api-models bumped from 0.0.3-next.27 to 0.0.3-next.28 + +## [0.0.3-next.27](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.26...api-auth-entity-storage-service-v0.0.3-next.27) (2026-04-23) + + +### Bug Fixes + +* handling the tid as undefined at refresh when empty ([#104](https://github.com/iotaledger/twin-api/issues/104)) ([d874a4a](https://github.com/iotaledger/twin-api/commit/d874a4a542c5fd45bb1bd56650247f844d056643)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.26 to 0.0.3-next.27 + * @twin.org/api-core bumped from 0.0.3-next.26 to 0.0.3-next.27 + * @twin.org/api-models bumped from 0.0.3-next.26 to 0.0.3-next.27 + +## [0.0.3-next.26](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.25...api-auth-entity-storage-service-v0.0.3-next.26) (2026-04-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.25 to 0.0.3-next.26 + * @twin.org/api-core bumped from 0.0.3-next.25 to 0.0.3-next.26 + * @twin.org/api-models bumped from 0.0.3-next.25 to 0.0.3-next.26 + +## [0.0.3-next.25](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.24...api-auth-entity-storage-service-v0.0.3-next.25) (2026-04-14) + + +### Features + +* auth header processor use user entity storage directly ([93084cf](https://github.com/iotaledger/twin-api/commit/93084cfed8f3ab16c80a1ab1bc90b1d9fc14bd25)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.24 to 0.0.3-next.25 + * @twin.org/api-core bumped from 0.0.3-next.24 to 0.0.3-next.25 + * @twin.org/api-models bumped from 0.0.3-next.24 to 0.0.3-next.25 + +## [0.0.3-next.24](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.23...api-auth-entity-storage-service-v0.0.3-next.24) (2026-04-14) + + +### Features + +* remove auth service dependency on admin service ([4aeb211](https://github.com/iotaledger/twin-api/commit/4aeb211a5ffcf2eaea89a6841faea3ac3069ef89)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.23 to 0.0.3-next.24 + * @twin.org/api-core bumped from 0.0.3-next.23 to 0.0.3-next.24 + * @twin.org/api-models bumped from 0.0.3-next.23 to 0.0.3-next.24 + +## [0.0.3-next.23](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.22...api-auth-entity-storage-service-v0.0.3-next.23) (2026-04-14) + + +### Features + +* auth enhancements ([#93](https://github.com/iotaledger/twin-api/issues/93)) ([921a50c](https://github.com/iotaledger/twin-api/commit/921a50cd89d26e530a6be6174a5a803060fa0eb6)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.22 to 0.0.3-next.23 + * @twin.org/api-core bumped from 0.0.3-next.22 to 0.0.3-next.23 + * @twin.org/api-models bumped from 0.0.3-next.22 to 0.0.3-next.23 + +## [0.0.3-next.22](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.21...api-auth-entity-storage-service-v0.0.3-next.22) (2026-03-27) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.21 to 0.0.3-next.22 + * @twin.org/api-core bumped from 0.0.3-next.21 to 0.0.3-next.22 + * @twin.org/api-models bumped from 0.0.3-next.21 to 0.0.3-next.22 + +## [0.0.3-next.21](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.20...api-auth-entity-storage-service-v0.0.3-next.21) (2026-03-11) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.20 to 0.0.3-next.21 + * @twin.org/api-core bumped from 0.0.3-next.20 to 0.0.3-next.21 + * @twin.org/api-models bumped from 0.0.3-next.20 to 0.0.3-next.21 + +## [0.0.3-next.20](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.19...api-auth-entity-storage-service-v0.0.3-next.20) (2026-02-09) + + +### Features + +* location encoding ([#79](https://github.com/iotaledger/twin-api/issues/79)) ([c684465](https://github.com/iotaledger/twin-api/commit/c684465f2a871376152472bdecb6aa230b1101a1)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.19 to 0.0.3-next.20 + * @twin.org/api-core bumped from 0.0.3-next.19 to 0.0.3-next.20 + * @twin.org/api-models bumped from 0.0.3-next.19 to 0.0.3-next.20 + +## [0.0.3-next.19](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.18...api-auth-entity-storage-service-v0.0.3-next.19) (2026-02-06) + + +### Features + +* user admin service ([#77](https://github.com/iotaledger/twin-api/issues/77)) ([c8491df](https://github.com/iotaledger/twin-api/commit/c8491df7b07c1f45560c8a78c6adc806d0ececbb)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.18 to 0.0.3-next.19 + * @twin.org/api-core bumped from 0.0.3-next.18 to 0.0.3-next.19 + * @twin.org/api-models bumped from 0.0.3-next.18 to 0.0.3-next.19 + +## [0.0.3-next.18](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.17...api-auth-entity-storage-service-v0.0.3-next.18) (2026-02-04) + + +### Features + +* tenant api and scopes ([#75](https://github.com/iotaledger/twin-api/issues/75)) ([c663141](https://github.com/iotaledger/twin-api/commit/c663141091e8974d769f8f9904ecdab009ebd083)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.17 to 0.0.3-next.18 + * @twin.org/api-core bumped from 0.0.3-next.17 to 0.0.3-next.18 + * @twin.org/api-models bumped from 0.0.3-next.17 to 0.0.3-next.18 + +## [0.0.3-next.17](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.16...api-auth-entity-storage-service-v0.0.3-next.17) (2026-01-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.16 to 0.0.3-next.17 + * @twin.org/api-core bumped from 0.0.3-next.16 to 0.0.3-next.17 + * @twin.org/api-models bumped from 0.0.3-next.16 to 0.0.3-next.17 + +## [0.0.3-next.16](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.15...api-auth-entity-storage-service-v0.0.3-next.16) (2026-01-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.15 to 0.0.3-next.16 + * @twin.org/api-core bumped from 0.0.3-next.15 to 0.0.3-next.16 + * @twin.org/api-models bumped from 0.0.3-next.15 to 0.0.3-next.16 + +## [0.0.3-next.15](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.14...api-auth-entity-storage-service-v0.0.3-next.15) (2026-01-22) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.14 to 0.0.3-next.15 + * @twin.org/api-core bumped from 0.0.3-next.14 to 0.0.3-next.15 + * @twin.org/api-models bumped from 0.0.3-next.14 to 0.0.3-next.15 + +## [0.0.3-next.14](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.13...api-auth-entity-storage-service-v0.0.3-next.14) (2026-01-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.13 to 0.0.3-next.14 + * @twin.org/api-core bumped from 0.0.3-next.13 to 0.0.3-next.14 + * @twin.org/api-models bumped from 0.0.3-next.13 to 0.0.3-next.14 + +## [0.0.3-next.13](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.12...api-auth-entity-storage-service-v0.0.3-next.13) (2026-01-19) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.12 to 0.0.3-next.13 + * @twin.org/api-core bumped from 0.0.3-next.12 to 0.0.3-next.13 + * @twin.org/api-models bumped from 0.0.3-next.12 to 0.0.3-next.13 + +## [0.0.3-next.12](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.11...api-auth-entity-storage-service-v0.0.3-next.12) (2026-01-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.11 to 0.0.3-next.12 + * @twin.org/api-core bumped from 0.0.3-next.11 to 0.0.3-next.12 + * @twin.org/api-models bumped from 0.0.3-next.11 to 0.0.3-next.12 + +## [0.0.3-next.11](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.10...api-auth-entity-storage-service-v0.0.3-next.11) (2026-01-08) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.10 to 0.0.3-next.11 + * @twin.org/api-core bumped from 0.0.3-next.10 to 0.0.3-next.11 + * @twin.org/api-models bumped from 0.0.3-next.10 to 0.0.3-next.11 + +## [0.0.3-next.10](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.9...api-auth-entity-storage-service-v0.0.3-next.10) (2026-01-05) + + +### Bug Fixes + +* remove token from return payload ([eaa4266](https://github.com/iotaledger/twin-api/commit/eaa42661b8540881b0751f5d2513258b3413f3ef)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.9 to 0.0.3-next.10 + * @twin.org/api-core bumped from 0.0.3-next.9 to 0.0.3-next.10 + * @twin.org/api-models bumped from 0.0.3-next.9 to 0.0.3-next.10 + +## [0.0.3-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.8...api-auth-entity-storage-service-v0.0.3-next.9) (2026-01-05) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add livez endpoint ([#57](https://github.com/iotaledger/twin-api/issues/57)) ([ef007db](https://github.com/iotaledger/twin-api/commit/ef007db8201736dd3053211f849ffd03baaa485e)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* modify authHeaderProcessor to retain token in response body ([#53](https://github.com/iotaledger/twin-api/issues/53)) ([5d9ae76](https://github.com/iotaledger/twin-api/commit/5d9ae76b5b52a8e10dac391b2d5784638a186583)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Bug Fixes + +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.8 to 0.0.3-next.9 + * @twin.org/api-core bumped from 0.0.3-next.8 to 0.0.3-next.9 + * @twin.org/api-models bumped from 0.0.3-next.8 to 0.0.3-next.9 + +## [0.0.3-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.7...api-auth-entity-storage-service-v0.0.3-next.8) (2025-12-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.7 to 0.0.3-next.8 + * @twin.org/api-core bumped from 0.0.3-next.7 to 0.0.3-next.8 + * @twin.org/api-models bumped from 0.0.3-next.7 to 0.0.3-next.8 + +## [0.0.3-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.6...api-auth-entity-storage-service-v0.0.3-next.7) (2025-11-26) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.6 to 0.0.3-next.7 + * @twin.org/api-core bumped from 0.0.3-next.6 to 0.0.3-next.7 + * @twin.org/api-models bumped from 0.0.3-next.6 to 0.0.3-next.7 + +## [0.0.3-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.5...api-auth-entity-storage-service-v0.0.3-next.6) (2025-11-20) + + +### Features + +* check tenant id in auth if set ([66f7337](https://github.com/iotaledger/twin-api/commit/66f73374d3cf4c1c85ea96ec74bb30712fb84dd7)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.5 to 0.0.3-next.6 + * @twin.org/api-core bumped from 0.0.3-next.5 to 0.0.3-next.6 + * @twin.org/api-models bumped from 0.0.3-next.5 to 0.0.3-next.6 + +## [0.0.3-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.4...api-auth-entity-storage-service-v0.0.3-next.5) (2025-11-14) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.4 to 0.0.3-next.5 + * @twin.org/api-core bumped from 0.0.3-next.4 to 0.0.3-next.5 + * @twin.org/api-models bumped from 0.0.3-next.4 to 0.0.3-next.5 + +## [0.0.3-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.3...api-auth-entity-storage-service-v0.0.3-next.4) (2025-11-14) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Bug Fixes + +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.3 to 0.0.3-next.4 + * @twin.org/api-core bumped from 0.0.3-next.3 to 0.0.3-next.4 + * @twin.org/api-models bumped from 0.0.3-next.3 to 0.0.3-next.4 + +## [0.0.3-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.2...api-auth-entity-storage-service-v0.0.3-next.3) (2025-11-14) + + +### Bug Fixes + +* include org in context ids from jwt ([a12cfdd](https://github.com/iotaledger/twin-api/commit/a12cfdddb05e2ed0300b26f3d7c0cfc033e59bd3)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.2 to 0.0.3-next.3 + * @twin.org/api-core bumped from 0.0.3-next.2 to 0.0.3-next.3 + * @twin.org/api-models bumped from 0.0.3-next.2 to 0.0.3-next.3 + +## [0.0.3-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.1...api-auth-entity-storage-service-v0.0.3-next.2) (2025-11-12) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.1 to 0.0.3-next.2 + * @twin.org/api-core bumped from 0.0.3-next.1 to 0.0.3-next.2 + * @twin.org/api-models bumped from 0.0.3-next.1 to 0.0.3-next.2 + +## [0.0.3-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.3-next.0...api-auth-entity-storage-service-v0.0.3-next.1) (2025-11-10) + + +### Features + +* add context id features ([#42](https://github.com/iotaledger/twin-api/issues/42)) ([0186055](https://github.com/iotaledger/twin-api/commit/0186055c48afde842a4254b4df9ac9249c40fe40)) +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.3-next.0 to 0.0.3-next.1 + * @twin.org/api-core bumped from 0.0.3-next.0 to 0.0.3-next.1 + * @twin.org/api-models bumped from 0.0.3-next.0 to 0.0.3-next.1 + +## [0.0.2-next.13](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.12...api-auth-entity-storage-service-v0.0.2-next.13) (2025-10-09) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.12 to 0.0.2-next.13 + * @twin.org/api-core bumped from 0.0.2-next.12 to 0.0.2-next.13 + * @twin.org/api-models bumped from 0.0.2-next.12 to 0.0.2-next.13 + +## [0.0.2-next.12](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.11...api-auth-entity-storage-service-v0.0.2-next.12) (2025-10-09) + + +### Features + +* add validate-locales ([cdba610](https://github.com/iotaledger/twin-api/commit/cdba610a0acb5022d2e3ce729732e6646a297e5e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.11 to 0.0.2-next.12 + * @twin.org/api-core bumped from 0.0.2-next.11 to 0.0.2-next.12 + * @twin.org/api-models bumped from 0.0.2-next.11 to 0.0.2-next.12 + +## [0.0.2-next.11](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.10...api-auth-entity-storage-service-v0.0.2-next.11) (2025-09-29) + + +### Features + +* update IComponent signatures ([915ce37](https://github.com/iotaledger/twin-api/commit/915ce37712326ab4aa6869c350eabaa4622e8430)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.10 to 0.0.2-next.11 + * @twin.org/api-core bumped from 0.0.2-next.10 to 0.0.2-next.11 + * @twin.org/api-models bumped from 0.0.2-next.10 to 0.0.2-next.11 + +## [0.0.2-next.10](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.9...api-auth-entity-storage-service-v0.0.2-next.10) (2025-09-23) + + +### Features + +* use new extractBearer method ([3e0cc54](https://github.com/iotaledger/twin-api/commit/3e0cc5462c06f59a6b744386eeff8326e5abbc95)) +* use new extractBearerToken method ([df654e9](https://github.com/iotaledger/twin-api/commit/df654e9caee5bd62f0be36f7be9902c8fab6ead6)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.9 to 0.0.2-next.10 + * @twin.org/api-core bumped from 0.0.2-next.9 to 0.0.2-next.10 + * @twin.org/api-models bumped from 0.0.2-next.9 to 0.0.2-next.10 + +## [0.0.2-next.9](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.8...api-auth-entity-storage-service-v0.0.2-next.9) (2025-08-29) + + +### Features + +* eslint migration to flat config ([0dd5820](https://github.com/iotaledger/twin-api/commit/0dd5820e3af97350fd08b8d226f4a6c1a9246805)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.8 to 0.0.2-next.9 + * @twin.org/api-core bumped from 0.0.2-next.8 to 0.0.2-next.9 + * @twin.org/api-models bumped from 0.0.2-next.8 to 0.0.2-next.9 + +## [0.0.2-next.8](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.7...api-auth-entity-storage-service-v0.0.2-next.8) (2025-08-21) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.7 to 0.0.2-next.8 + * @twin.org/api-core bumped from 0.0.2-next.7 to 0.0.2-next.8 + * @twin.org/api-models bumped from 0.0.2-next.7 to 0.0.2-next.8 + +## [0.0.2-next.7](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.6...api-auth-entity-storage-service-v0.0.2-next.7) (2025-08-20) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.6 to 0.0.2-next.7 + * @twin.org/api-core bumped from 0.0.2-next.6 to 0.0.2-next.7 + * @twin.org/api-models bumped from 0.0.2-next.6 to 0.0.2-next.7 + +## [0.0.2-next.6](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.5...api-auth-entity-storage-service-v0.0.2-next.6) (2025-08-19) + + +### Features + +* update framework core ([d8eebf2](https://github.com/iotaledger/twin-api/commit/d8eebf267fa2a0abaa84e58590496e9d20490cfa)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.5 to 0.0.2-next.6 + * @twin.org/api-core bumped from 0.0.2-next.5 to 0.0.2-next.6 + * @twin.org/api-models bumped from 0.0.2-next.5 to 0.0.2-next.6 + +## [0.0.2-next.5](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.4...api-auth-entity-storage-service-v0.0.2-next.5) (2025-07-25) + + +### Features + +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.4 to 0.0.2-next.5 + * @twin.org/api-core bumped from 0.0.2-next.4 to 0.0.2-next.5 + * @twin.org/api-models bumped from 0.0.2-next.4 to 0.0.2-next.5 + +## [0.0.2-next.4](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.3...api-auth-entity-storage-service-v0.0.2-next.4) (2025-07-25) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.3 to 0.0.2-next.4 + * @twin.org/api-core bumped from 0.0.2-next.3 to 0.0.2-next.4 + * @twin.org/api-models bumped from 0.0.2-next.3 to 0.0.2-next.4 + +## [0.0.2-next.3](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.2...api-auth-entity-storage-service-v0.0.2-next.3) (2025-07-24) + + +### Features + +* remove unused namespace ([08478f2](https://github.com/iotaledger/twin-api/commit/08478f27efda9beb0271fdb22f6972e918361965)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.2 to 0.0.2-next.3 + * @twin.org/api-core bumped from 0.0.2-next.2 to 0.0.2-next.3 + * @twin.org/api-models bumped from 0.0.2-next.2 to 0.0.2-next.3 + +## [0.0.2-next.2](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.1...api-auth-entity-storage-service-v0.0.2-next.2) (2025-07-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.1 to 0.0.2-next.2 + * @twin.org/api-core bumped from 0.0.2-next.1 to 0.0.2-next.2 + * @twin.org/api-models bumped from 0.0.2-next.1 to 0.0.2-next.2 + +## [0.0.2-next.1](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.2-next.0...api-auth-entity-storage-service-v0.0.2-next.1) (2025-07-08) + + +### Features + +* add json-ld mime type processor and auth admin component ([8861791](https://github.com/iotaledger/twin-api/commit/88617916e23bfbca023dbae1976fe421983a02ff)) +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.2-next.0 to 0.0.2-next.1 + * @twin.org/api-core bumped from 0.0.2-next.0 to 0.0.2-next.1 + * @twin.org/api-models bumped from 0.0.2-next.0 to 0.0.2-next.1 + +## 0.0.1 (2025-07-03) + + +### Features + +* release to production ([70ee2d5](https://github.com/iotaledger/twin-api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from ^0.0.0 to ^0.0.1 + * @twin.org/api-core bumped from ^0.0.0 to ^0.0.1 + * @twin.org/api-models bumped from ^0.0.0 to ^0.0.1 + +## [0.0.1-next.36](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.1-next.35...api-auth-entity-storage-service-v0.0.1-next.36) (2025-06-17) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.35 to 0.0.1-next.36 + * @twin.org/api-core bumped from 0.0.1-next.35 to 0.0.1-next.36 + * @twin.org/api-models bumped from 0.0.1-next.35 to 0.0.1-next.36 + +## [0.0.1-next.35](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.1-next.34...api-auth-entity-storage-service-v0.0.1-next.35) (2025-06-11) + + +### Features + +* update dependencies ([1171dc4](https://github.com/iotaledger/twin-api/commit/1171dc416a9481737f6a640e3cf30145768f37e9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.34 to 0.0.1-next.35 + * @twin.org/api-core bumped from 0.0.1-next.34 to 0.0.1-next.35 + * @twin.org/api-models bumped from 0.0.1-next.34 to 0.0.1-next.35 + +## [0.0.1-next.34](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.1-next.33...api-auth-entity-storage-service-v0.0.1-next.34) (2025-05-27) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.33 to 0.0.1-next.34 + * @twin.org/api-core bumped from 0.0.1-next.33 to 0.0.1-next.34 + * @twin.org/api-models bumped from 0.0.1-next.33 to 0.0.1-next.34 + +## [0.0.1-next.33](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.1-next.32...api-auth-entity-storage-service-v0.0.1-next.33) (2025-04-17) + + +### Features + +* use shared store mechanism ([#19](https://github.com/iotaledger/twin-api/issues/19)) ([32116df](https://github.com/iotaledger/twin-api/commit/32116df3b4380a30137f5056f242a5c99afa2df9)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.32 to 0.0.1-next.33 + * @twin.org/api-core bumped from 0.0.1-next.32 to 0.0.1-next.33 + * @twin.org/api-models bumped from 0.0.1-next.32 to 0.0.1-next.33 + +## [0.0.1-next.32](https://github.com/iotaledger/twin-api/compare/api-auth-entity-storage-service-v0.0.1-next.31...api-auth-entity-storage-service-v0.0.1-next.32) (2025-03-28) + + +### Miscellaneous Chores + +* **api-auth-entity-storage-service:** Synchronize repo versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * @twin.org/api-auth-entity-storage-models bumped from 0.0.1-next.31 to 0.0.1-next.32 + * @twin.org/api-core bumped from 0.0.1-next.31 to 0.0.1-next.32 + * @twin.org/api-models bumped from 0.0.1-next.31 to 0.0.1-next.32 + +## v0.0.1-next.31 + +- Initial Release diff --git a/packages/api-auth-service/docs/examples.md b/packages/api-auth-service/docs/examples.md new file mode 100644 index 0000000..dcde42b --- /dev/null +++ b/packages/api-auth-service/docs/examples.md @@ -0,0 +1,137 @@ +# Auth Service Examples + +These snippets show how to apply rate limiting, record authentication audit entries and allow privileged callers to override the tenant of a request. + +## EntityStorageAuthenticationRateService + +```typescript +import { EntityStorageAuthenticationRateService } from '@3sixty/api-auth-service'; +import { BaseError, GeneralError } from '@3sixty/core'; +import { TooManyRequestsError } from '@3sixty/api-models'; + +const rateService = new EntityStorageAuthenticationRateService({ + config: { + cleanupIntervalMinutes: 5 + } +}); + +console.log(rateService.className()); // EntityStorageAuthenticationRateService + +await rateService.registerAction('login', { + maxAttempts: 3, + windowMinutes: 15 +}); + +await rateService.start('default'); + +await rateService.check('login', 'alice@example.org'); +await rateService.check('login', 'alice@example.org'); +await rateService.check('login', 'alice@example.org'); + +try { + await rateService.check('login', 'alice@example.org'); +} catch (error) { + if (BaseError.isErrorName(error, TooManyRequestsError.CLASS_NAME)) { + const tooMany = error as TooManyRequestsError; + console.log(tooMany.properties?.retryAfterSeconds); // 900 + console.log(tooMany.properties?.nextRequestTime); // 2026-04-13T10:15:00.000Z + } +} + +await rateService.clear('login', 'alice@example.org'); +await rateService.unregisterAction('login'); + +try { + await rateService.check('login', 'alice@example.org'); +} catch (error) { + if (BaseError.isErrorName(error, GeneralError.CLASS_NAME)) { + console.log((error as GeneralError).name); // GeneralError + } +} + +await rateService.stop('default'); +``` + +## EntityStorageAuthenticationAuditService + +```typescript +import { EntityStorageAuthenticationAuditService } from '@3sixty/api-auth-service'; + +const auditService = new EntityStorageAuthenticationAuditService({ + config: { + ipHashSalt: 'StrongServerSideSaltForAuditHashing123' + } +}); + +console.log(auditService.className()); // EntityStorageAuthenticationAuditService + +const createdAuditId = await auditService.create({ + event: 'login-failure', + actorId: 'did:example:user:alice', + nodeId: 'did:example:node:eu-west-1', + organizationId: 'did:example:org:core', + tenantId: 'did:example:tenant:alpha', + data: { + reason: 'invalid-password' + } +}); + +const auditPage = await auditService.query( + { + actorId: 'did:example:user:alice', + event: 'login-failure', + startDate: '2026-04-01T00:00:00.000Z', + endDate: '2026-04-30T23:59:59.999Z' + }, + undefined, + 50 +); + +console.log(createdAuditId); // 018f2f67bb9d4a0caad8386f56df85ce +console.log(auditPage.entries.length); // 1 +console.log(auditPage.cursor); // eyJpZCI6IjAxOGYyZjY3YmI5ZDRhMGNhYWQ4Mzg2ZjU2ZGY4NWNlIn0= +``` + +## TenantOverrideProcessor + +The processor runs after `AuthHeaderProcessor`. A caller holding the escalated privilege role can supply the `override-tenant` query parameter to act on another tenant. + +```typescript +import { TenantOverrideProcessor } from '@3sixty/api-auth-service'; + +const processor = new TenantOverrideProcessor({ + tenantAdminComponentType: 'tenant-admin', + authorizationComponentType: 'authorization', + config: { + escalatedPrivilegeRole: 'global-admin', + authorizationModelId: 'system' + } +}); + +console.log(processor.className()); // TenantOverrideProcessor + +const request = { + method: 'get', + url: '/authentication/audit?override-tenant=did:example:tenant:beta', + query: { + 'override-tenant': 'did:example:tenant:beta' + } +}; + +const response = {}; +const contextIds = { + user: 'did:example:user:admin', + tenant: 'did:example:tenant:alpha' +}; +const processorState: { [id: string]: unknown } = {}; + +await processor.pre( + request, + response, + { operationId: 'auditQuery', path: '/' }, + contextIds, + processorState +); + +console.log(contextIds.tenant); // did:example:tenant:beta +``` diff --git a/packages/api-auth-service/docs/open-api/spec.json b/packages/api-auth-service/docs/open-api/spec.json new file mode 100644 index 0000000..e3fd182 --- /dev/null +++ b/packages/api-auth-service/docs/open-api/spec.json @@ -0,0 +1,911 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "TWIN - Test Endpoints", + "description": "REST API for TWIN - Test Endpoints.", + "version": "1.0.0", + "license": { + "name": "Apache 2.0 License", + "url": "https://opensource.org/licenses/Apache-2.0" + } + }, + "servers": [ + { + "url": "https://localhost" + } + ], + "tags": [ + { + "name": "Authentication Audit", + "description": "Authentication audit endpoints for the REST server." + } + ], + "paths": { + "/authentication/audit": { + "post": { + "operationId": "authenticationAuditCreate", + "summary": "Create an authentication audit entry", + "tags": [ + "Authentication Audit" + ], + "security": [ + { + "jwtBearerAuthScheme": [] + } + ], + "requestBody": { + "description": "Create an authentication audit entry.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuditCreateRequest" + }, + "examples": { + "authenticationAuditCreateRequestExample": { + "summary": "The request to create an authentication audit entry.", + "value": { + "actorId": "user@example.com", + "event": "login-success", + "data": { + "organizationIdentity": "did:example:org1" + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The rest request ended in created response.", + "headers": { + "location": { + "schema": { + "type": "string" + }, + "description": "e.g. 018f0b53d5d5704fa3a06d6ed2478575" + } + } + }, + "400": { + "description": "The server cannot process the request, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "GeneralError", + "message": "errorMessage", + "properties": { + "foo": "bar" + } + } + } + } + } + } + }, + "401": { + "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "UnauthorizedError", + "message": "errorMessage" + } + } + } + } + } + }, + "500": { + "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "InternalServerError", + "message": "errorMessage" + } + } + } + } + } + } + } + }, + "get": { + "operationId": "authenticationAuditQuery", + "summary": "Query authentication audit entries", + "tags": [ + "Authentication Audit" + ], + "parameters": [ + { + "name": "actorId", + "description": "The actor identifier to filter by.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "example": "user@example.com" + }, + { + "name": "organizationId", + "description": "The organisation identifier to filter by.", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "tenantId", + "description": "The tenant identifier to filter by.", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "nodeId", + "description": "The node identifier to filter by.", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "event", + "description": "The event to filter by.", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/AuthAuditEvent" + }, + { + "type": "string" + } + ] + }, + "example": "login-success" + }, + { + "name": "startDate", + "description": "The inclusive start date for filtering, in ISO 8601 format.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "example": "2026-01-01T00:00:00.000Z" + }, + { + "name": "endDate", + "description": "The inclusive end date for filtering, in ISO 8601 format.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "example": "2026-01-31T23:59:59.999Z" + }, + { + "name": "cursor", + "description": "The pagination cursor.", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "description": "The maximum number of results to return.", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "example": "50" + } + ], + "security": [ + { + "jwtBearerAuthScheme": [] + } + ], + "responses": { + "200": { + "description": "Response from querying authentication audit entries.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuditQueryResponse" + }, + "examples": { + "authenticationAuditQueryResponseExample": { + "summary": "The response for querying authentication audit entries.", + "value": { + "entries": [ + { + "id": "018f0b53d5d5704fa3a06d6ed2478575", + "actorId": "user@example.com", + "dateCreated": "2026-01-12T09:05:23.123Z", + "event": "login-success", + "data": { + "organizationIdentity": "did:example:org1" + } + } + ], + "cursor": "next-cursor" + } + } + } + } + } + }, + "400": { + "description": "The server cannot process the request, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "GeneralError", + "message": "errorMessage", + "properties": { + "foo": "bar" + } + } + } + } + } + } + }, + "401": { + "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "UnauthorizedError", + "message": "errorMessage" + } + } + } + } + } + }, + "500": { + "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "InternalServerError", + "message": "errorMessage" + } + } + } + } + } + } + } + } + }, + "/authentication/audit/{id}": { + "get": { + "operationId": "authenticationAuditGet", + "summary": "Get an authentication audit entry", + "tags": [ + "Authentication Audit" + ], + "parameters": [ + { + "name": "id", + "description": "The audit entry id.", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "style": "simple", + "example": "018f0b53d5d5704fa3a06d6ed2478575" + } + ], + "security": [ + { + "jwtBearerAuthScheme": [] + } + ], + "responses": { + "200": { + "description": "Response from getting an authentication audit entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationAuditEntry" + }, + "examples": { + "authenticationAuditGetResponseExample": { + "summary": "The response for getting an authentication audit entry.", + "value": { + "id": "018f0b53d5d5704fa3a06d6ed2478575", + "actorId": "user@example.com", + "dateCreated": "2026-01-12T09:05:23.123Z", + "event": "login-success", + "data": { + "organizationIdentity": "did:example:org1" + } + } + } + } + } + } + }, + "400": { + "description": "The server cannot process the request, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "GeneralError", + "message": "errorMessage", + "properties": { + "foo": "bar" + } + } + } + } + } + } + }, + "401": { + "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "UnauthorizedError", + "message": "errorMessage" + } + } + } + } + } + }, + "500": { + "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "InternalServerError", + "message": "errorMessage" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "authenticationAuditUpdate", + "summary": "Update an authentication audit entry", + "tags": [ + "Authentication Audit" + ], + "parameters": [ + { + "name": "id", + "description": "The audit entry id.", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "style": "simple", + "example": "018f0b53d5d5704fa3a06d6ed2478575" + } + ], + "security": [ + { + "jwtBearerAuthScheme": [] + } + ], + "requestBody": { + "description": "Update an authentication audit entry.", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuditUpdateRequest" + }, + "examples": { + "authenticationAuditUpdateRequestExample": { + "summary": "The request to update an authentication audit entry.", + "value": { + "data": { + "organizationIdentity": "did:example:org1" + } + } + } + } + } + } + }, + "responses": { + "204": { + "description": "The rest request ended in success with no data." + }, + "400": { + "description": "The server cannot process the request, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "GeneralError", + "message": "errorMessage", + "properties": { + "foo": "bar" + } + } + } + } + } + } + }, + "401": { + "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "UnauthorizedError", + "message": "errorMessage" + } + } + } + } + } + }, + "500": { + "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "InternalServerError", + "message": "errorMessage" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "authenticationAuditRemove", + "summary": "Remove an authentication audit entry", + "tags": [ + "Authentication Audit" + ], + "parameters": [ + { + "name": "id", + "description": "The audit entry id.", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "style": "simple", + "example": "018f0b53d5d5704fa3a06d6ed2478575" + } + ], + "security": [ + { + "jwtBearerAuthScheme": [] + } + ], + "responses": { + "204": { + "description": "The rest request ended in success with no data." + }, + "400": { + "description": "The server cannot process the request, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "GeneralError", + "message": "errorMessage", + "properties": { + "foo": "bar" + } + } + } + } + } + } + }, + "401": { + "description": "You are not authorized to use the API or no credentials were supplied, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "UnauthorizedError", + "message": "errorMessage" + } + } + } + } + } + }, + "500": { + "description": "The server has encountered a situation it does not know how to handle, see the content for more details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "exampleResponse": { + "value": { + "name": "InternalServerError", + "message": "errorMessage" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AuditCreateRequest": { + "description": "The body of the request.", + "type": "object", + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/components/schemas/AuthAuditEvent" + }, + { + "type": "string" + } + ], + "description": "The audit event that occurred." + }, + "actorId": { + "type": "string", + "description": "The actor identifier, could be e-mail, username, or other unique identifier." + }, + "nodeId": { + "type": "string", + "description": "The node identifier associated with the audit entry, if applicable." + }, + "organizationId": { + "type": "string", + "description": "The organization identifier associated with the audit entry, if applicable." + }, + "tenantId": { + "type": "string", + "description": "The tenant identifier associated with the audit entry, if applicable." + }, + "ipAddressHashes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The hashed IP addresses of the client." + }, + "userAgent": { + "type": "string", + "description": "The user agent string of the client." + }, + "correlationId": { + "type": "string", + "description": "The correlation ID for request tracing." + }, + "data": { + "description": "Additional data related to the audit entry, such as IP address, user agent, etc." + } + }, + "required": [ + "event" + ] + }, + "AuditQueryResponse": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthenticationAuditEntry" + }, + "description": "The returned audit entries." + }, + "cursor": { + "type": "string", + "description": "The cursor to retrieve the next page, if any." + } + }, + "required": [ + "entries" + ], + "description": "The response body." + }, + "AuditUpdateRequest": { + "description": "The body of the request.", + "type": "object", + "properties": { + "event": { + "anyOf": [ + { + "$ref": "#/components/schemas/AuthAuditEvent" + }, + { + "type": "string" + } + ], + "description": "The audit event that occurred." + }, + "actorId": { + "type": "string", + "description": "The actor identifier, could be e-mail, username, or other unique identifier." + }, + "nodeId": { + "type": "string", + "description": "The node identifier associated with the audit entry, if applicable." + }, + "organizationId": { + "type": "string", + "description": "The organization identifier associated with the audit entry, if applicable." + }, + "tenantId": { + "type": "string", + "description": "The tenant identifier associated with the audit entry, if applicable." + }, + "ipAddressHashes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The hashed IP addresses of the client." + }, + "userAgent": { + "type": "string", + "description": "The user agent string of the client." + }, + "correlationId": { + "type": "string", + "description": "The correlation ID for request tracing." + }, + "data": { + "description": "Additional data related to the audit entry, such as IP address, user agent, etc." + } + } + }, + "AuthAuditEvent": { + "description": "Supported authentication audit event values.", + "anyOf": [ + { + "const": "login-success", + "description": "Login success." + }, + { + "const": "login-failure", + "description": "Login failure." + }, + { + "const": "logout", + "description": "Logout." + }, + { + "const": "token-refreshed", + "description": "Token refreshed." + }, + { + "const": "account-created", + "description": "Account created." + }, + { + "const": "account-deleted", + "description": "Account deleted." + }, + { + "const": "account-updated", + "description": "Account updated." + }, + { + "const": "account-locked", + "description": "Account locked." + }, + { + "const": "account-unlocked", + "description": "Account unlocked." + }, + { + "const": "password-changed", + "description": "Password changed." + } + ] + }, + "AuthenticationAuditEntry": { + "description": "Contract definition for authentication audit entry.", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the audit entry." + }, + "event": { + "anyOf": [ + { + "$ref": "#/components/schemas/AuthAuditEvent" + }, + { + "type": "string" + } + ], + "description": "The audit event that occurred." + }, + "dateCreated": { + "type": "string", + "description": "The timestamp of the audit entry in ISO 8601 format." + }, + "actorId": { + "type": "string", + "description": "The actor identifier, could be e-mail, username, or other unique identifier." + }, + "nodeId": { + "type": "string", + "description": "The node identifier associated with the audit entry, if applicable." + }, + "organizationId": { + "type": "string", + "description": "The organization identifier associated with the audit entry, if applicable." + }, + "tenantId": { + "type": "string", + "description": "The tenant identifier associated with the audit entry, if applicable." + }, + "ipAddressHashes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The hashed IP addresses of the client." + }, + "userAgent": { + "type": "string", + "description": "The user agent string of the client." + }, + "correlationId": { + "type": "string", + "description": "The correlation ID for request tracing." + }, + "data": { + "description": "Additional data related to the audit entry, such as IP address, user agent, etc." + } + }, + "required": [ + "id", + "event", + "dateCreated" + ] + }, + "Error": { + "description": "Model to describe serialized error.", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name for the error." + }, + "message": { + "type": "string", + "description": "The message for the error as an i18n key." + }, + "source": { + "type": "string", + "description": "The source of the error." + }, + "properties": { + "type": "object", + "additionalProperties": {}, + "description": "Any additional information for the error." + }, + "stack": { + "type": "string", + "description": "The stack trace for the error." + }, + "cause": { + "$ref": "#/components/schemas/Error" + } + }, + "required": [ + "name", + "message" + ] + } + }, + "securitySchemes": { + "jwtBearerAuthScheme": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationAuditEntry.md b/packages/api-auth-service/docs/reference/classes/AuthenticationAuditEntry.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationAuditEntry.md rename to packages/api-auth-service/docs/reference/classes/AuthenticationAuditEntry.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationRateEntry.md b/packages/api-auth-service/docs/reference/classes/AuthenticationRateEntry.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/classes/AuthenticationRateEntry.md rename to packages/api-auth-service/docs/reference/classes/AuthenticationRateEntry.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/EntityStorageAuthenticationAuditService.md b/packages/api-auth-service/docs/reference/classes/EntityStorageAuthenticationAuditService.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/classes/EntityStorageAuthenticationAuditService.md rename to packages/api-auth-service/docs/reference/classes/EntityStorageAuthenticationAuditService.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/classes/EntityStorageAuthenticationRateService.md b/packages/api-auth-service/docs/reference/classes/EntityStorageAuthenticationRateService.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/classes/EntityStorageAuthenticationRateService.md rename to packages/api-auth-service/docs/reference/classes/EntityStorageAuthenticationRateService.md diff --git a/packages/api-auth-service/docs/reference/classes/TenantOverrideProcessor.md b/packages/api-auth-service/docs/reference/classes/TenantOverrideProcessor.md new file mode 100644 index 0000000..365f42f --- /dev/null +++ b/packages/api-auth-service/docs/reference/classes/TenantOverrideProcessor.md @@ -0,0 +1,119 @@ +# Class: TenantOverrideProcessor + +Processes the overrideTenant query parameter for routes that allow tenant override. +Requires the caller to hold the escalated privilege role. Runs after AuthHeaderProcessor so +authentication is always resolved in the caller's own partition first. + +## Implements + +- `IBaseRouteProcessor` + +## Constructors + +### Constructor + +> **new TenantOverrideProcessor**(`options?`): `TenantOverrideProcessor` + +Create a new instance of TenantOverrideProcessor. + +#### Parameters + +##### options? + +[`ITenantOverrideProcessorConstructorOptions`](../interfaces/ITenantOverrideProcessorConstructorOptions.md) + +Options for the processor. + +#### Returns + +`TenantOverrideProcessor` + +## Properties + +### OVERRIDE\_TENANT\_PARAM {#override_tenant_param} + +> `readonly` `static` **OVERRIDE\_TENANT\_PARAM**: `string` = `"override-tenant"` + +The query parameter name used to supply the override tenant ID. + +*** + +### DEFAULT\_ESCALATED\_PRIVILEGE\_ROLE {#default_escalated_privilege_role} + +> `readonly` `static` **DEFAULT\_ESCALATED\_PRIVILEGE\_ROLE**: `string` = `"global-admin"` + +The role string required to perform a tenant override. + +*** + +### CLASS\_NAME {#class_name} + +> `readonly` `static` **CLASS\_NAME**: `string` + +Runtime name for the class. + +## Methods + +### className() {#classname} + +> **className**(): `string` + +Returns the class name of the component. + +#### Returns + +`string` + +The class name of the component. + +#### Implementation of + +`IBaseRouteProcessor.className` + +*** + +### pre() {#pre} + +> **pre**(`request`, `response`, `route`, `contextIds`, `processorState`): `Promise`\<`void`\> + +Pre process the REST request for the specified route. + +#### Parameters + +##### request + +`IHttpServerRequest` + +The incoming request. + +##### response + +`IHttpResponse` + +The outgoing response. + +##### route + +`IBaseRoute` \| `undefined` + +The route to process. + +##### contextIds + +`IContextIds` + +The context IDs of the request. + +##### processorState + +The state handed through the processors. + +#### Returns + +`Promise`\<`void`\> + +A promise that resolves when the tenant override has been applied or skipped. + +#### Implementation of + +`IBaseRouteProcessor.pre` diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditCreate.md b/packages/api-auth-service/docs/reference/functions/authenticationAuditCreate.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditCreate.md rename to packages/api-auth-service/docs/reference/functions/authenticationAuditCreate.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditGet.md b/packages/api-auth-service/docs/reference/functions/authenticationAuditGet.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditGet.md rename to packages/api-auth-service/docs/reference/functions/authenticationAuditGet.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditQuery.md b/packages/api-auth-service/docs/reference/functions/authenticationAuditQuery.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditQuery.md rename to packages/api-auth-service/docs/reference/functions/authenticationAuditQuery.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditRemove.md b/packages/api-auth-service/docs/reference/functions/authenticationAuditRemove.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditRemove.md rename to packages/api-auth-service/docs/reference/functions/authenticationAuditRemove.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditUpdate.md b/packages/api-auth-service/docs/reference/functions/authenticationAuditUpdate.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/authenticationAuditUpdate.md rename to packages/api-auth-service/docs/reference/functions/authenticationAuditUpdate.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/functions/generateRestRoutesAuthenticationAudit.md b/packages/api-auth-service/docs/reference/functions/generateRestRoutesAuthenticationAudit.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/functions/generateRestRoutesAuthenticationAudit.md rename to packages/api-auth-service/docs/reference/functions/generateRestRoutesAuthenticationAudit.md diff --git a/packages/api-auth-service/docs/reference/functions/initSchema.md b/packages/api-auth-service/docs/reference/functions/initSchema.md new file mode 100644 index 0000000..d13a5f3 --- /dev/null +++ b/packages/api-auth-service/docs/reference/functions/initSchema.md @@ -0,0 +1,9 @@ +# Function: initSchema() + +> **initSchema**(): `void` + +Initialize the schema for the authentication service. + +## Returns + +`void` diff --git a/packages/api-auth-service/docs/reference/index.md b/packages/api-auth-service/docs/reference/index.md new file mode 100644 index 0000000..7f31602 --- /dev/null +++ b/packages/api-auth-service/docs/reference/index.md @@ -0,0 +1,33 @@ +# @twin.org/api-auth-service + +## Classes + +- [AuthenticationAuditEntry](classes/AuthenticationAuditEntry.md) +- [AuthenticationRateEntry](classes/AuthenticationRateEntry.md) +- [TenantOverrideProcessor](classes/TenantOverrideProcessor.md) +- [EntityStorageAuthenticationAuditService](classes/EntityStorageAuthenticationAuditService.md) +- [EntityStorageAuthenticationRateService](classes/EntityStorageAuthenticationRateService.md) + +## Interfaces + +- [IEntityStorageAuthenticationAuditServiceConfig](interfaces/IEntityStorageAuthenticationAuditServiceConfig.md) +- [IEntityStorageAuthenticationAuditServiceConstructorOptions](interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md) +- [IEntityStorageAuthenticationRateServiceConfig](interfaces/IEntityStorageAuthenticationRateServiceConfig.md) +- [IEntityStorageAuthenticationRateServiceConstructorOptions](interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md) +- [ITenantOverrideProcessorConfig](interfaces/ITenantOverrideProcessorConfig.md) +- [ITenantOverrideProcessorConstructorOptions](interfaces/ITenantOverrideProcessorConstructorOptions.md) + +## Variables + +- [restEntryPoints](variables/restEntryPoints.md) +- [tagsAuthenticationAudit](variables/tagsAuthenticationAudit.md) + +## Functions + +- [generateRestRoutesAuthenticationAudit](functions/generateRestRoutesAuthenticationAudit.md) +- [authenticationAuditCreate](functions/authenticationAuditCreate.md) +- [authenticationAuditGet](functions/authenticationAuditGet.md) +- [authenticationAuditUpdate](functions/authenticationAuditUpdate.md) +- [authenticationAuditRemove](functions/authenticationAuditRemove.md) +- [authenticationAuditQuery](functions/authenticationAuditQuery.md) +- [initSchema](functions/initSchema.md) diff --git a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md b/packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md rename to packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConfig.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md b/packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md rename to packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationAuditServiceConstructorOptions.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md b/packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md rename to packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConfig.md diff --git a/packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md b/packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md rename to packages/api-auth-service/docs/reference/interfaces/IEntityStorageAuthenticationRateServiceConstructorOptions.md diff --git a/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConfig.md b/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConfig.md new file mode 100644 index 0000000..01a2144 --- /dev/null +++ b/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConfig.md @@ -0,0 +1,33 @@ +# Interface: ITenantOverrideProcessorConfig + +Configuration for the TenantOverrideProcessor. + +## Properties + +### includeErrorStack? {#includeerrorstack} + +> `optional` **includeErrorStack?**: `boolean` + +Include the stack with errors. + +*** + +### escalatedPrivilegeRole? {#escalatedprivilegerole} + +> `optional` **escalatedPrivilegeRole?**: `string` + +The role value that grants escalated privilege for cross-tenant access. Defaults to "global-admin". + +*** + +### authorizationModelId? {#authorizationmodelid} + +> `optional` **authorizationModelId?**: `string` + +The authorization model ID to use for REST requests. + +#### Default + +```ts +rest +``` diff --git a/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConstructorOptions.md b/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConstructorOptions.md new file mode 100644 index 0000000..0454111 --- /dev/null +++ b/packages/api-auth-service/docs/reference/interfaces/ITenantOverrideProcessorConstructorOptions.md @@ -0,0 +1,39 @@ +# Interface: ITenantOverrideProcessorConstructorOptions + +Options for the TenantOverrideProcessor constructor. + +## Properties + +### tenantAdminComponentType? {#tenantadmincomponenttype} + +> `optional` **tenantAdminComponentType?**: `string` + +The tenant admin component type. + +#### Default + +```ts +tenant-admin +``` + +*** + +### authorizationComponentType? {#authorizationcomponenttype} + +> `optional` **authorizationComponentType?**: `string` + +The authorization component type. + +#### Default + +```ts +authorization +``` + +*** + +### config? {#config} + +> `optional` **config?**: [`ITenantOverrideProcessorConfig`](ITenantOverrideProcessorConfig.md) + +Configuration for the processor. diff --git a/packages/api-auth-service/docs/reference/variables/restEntryPoints.md b/packages/api-auth-service/docs/reference/variables/restEntryPoints.md new file mode 100644 index 0000000..0dce4ee --- /dev/null +++ b/packages/api-auth-service/docs/reference/variables/restEntryPoints.md @@ -0,0 +1,5 @@ +# Variable: restEntryPoints + +> `const` **restEntryPoints**: `IRestRouteEntryPoint`[] + +REST entry points for the authentication, authentication admin, and authentication audit services. diff --git a/packages/api-auth-entity-storage-service/docs/reference/variables/tagsAuthenticationAudit.md b/packages/api-auth-service/docs/reference/variables/tagsAuthenticationAudit.md similarity index 100% rename from packages/api-auth-entity-storage-service/docs/reference/variables/tagsAuthenticationAudit.md rename to packages/api-auth-service/docs/reference/variables/tagsAuthenticationAudit.md diff --git a/packages/api-auth-service/locales/en.json b/packages/api-auth-service/locales/en.json new file mode 100644 index 0000000..6255647 --- /dev/null +++ b/packages/api-auth-service/locales/en.json @@ -0,0 +1,22 @@ +{ + "error": { + "entityStorageAuthenticationAuditService": { + "auditEntryNotFound": "The audit entry with the specified identifier could not be found \"{notFoundId}\"", + "getAuditEntryFailed": "Getting the audit entry failed", + "updateAuditEntryFailed": "Updating the audit entry failed", + "removeAuditEntryFailed": "Removing the audit entry failed" + }, + "entityStorageAuthenticationRateService": { + "actionConfigMissing": "No rate-limit configuration exists for action \"{action}\".", + "rateLimitExceeded": "The rate limit for action \"{action}\" has been exceeded. Retry after {retryAfterSeconds} seconds." + }, + "validation": { + "saltEntropyTooLow": "Salt must have at least 8 unique characters for sufficient entropy." + }, + "tenantOverrideProcessor": { + "insufficientRoleForTenantOverride": "Caller does not hold the required role for tenant override.", + "missingUserIdForTenantOverride": "Missing user id for tenant override.", + "tenantNotFound": "Tenant \"{notFoundId}\" not found." + } + } +} diff --git a/packages/api-auth-service/package.json b/packages/api-auth-service/package.json new file mode 100644 index 0000000..d8e207a --- /dev/null +++ b/packages/api-auth-service/package.json @@ -0,0 +1,95 @@ +{ + "name": "@3sixty/api-auth-service", + "version": "0.20.0-next.0", + "description": "Authentication audit and rate limiting services, tenant override processing and audit REST routes backed by entity storage.", + "repository": { + "type": "git", + "url": "git+https://github.com/3sixtyglobal/api-auth.git", + "directory": "packages/api-auth-service" + }, + "author": "martyn.janes@iota.org", + "license": "Apache-2.0", + "type": "module", + "engines": { + "node": ">=24.0.0" + }, + "scripts": { + "clean": "rimraf dist coverage .cache", + "build": "tstransform", + "dev": "nodemon --watch src --ext ts --exec \"pnpm run build\"", + "test": "vitest --run --config ./vitest.config.ts", + "test:build": "tstransform -p ./tests/tsconfig.json", + "validate-locales": "validate-locales", + "test:coverage": "vitest --run --coverage --config ./vitest.config.ts", + "docs:clean": "rimraf docs/reference", + "docs:generate": "typedoc --options typedoc.config.mjs", + "docs:api": "ts-to-openapi ./ts-to-openapi.json ./docs/open-api/spec.json", + "docs": "pnpm run docs:clean && pnpm run docs:generate && pnpm run docs:api", + "dist": "pnpm run clean && concurrently -g -n build,locales,types,test \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\" \"pnpm run test\"", + "dist:no-test": "pnpm run clean && concurrently -g -n build,locales,types \"pnpm run build\" \"pnpm run validate-locales\" \"pnpm run test:build\"" + }, + "dependencies": { + "@3sixty/api-auth-models": "0.20.0-next.0", + "@3sixty/api-models": "next", + "@3sixty/authorization-models": "next", + "@3sixty/background-task-models": "next", + "@3sixty/context": "next", + "@3sixty/core": "next", + "@3sixty/crypto": "next", + "@3sixty/entity": "next", + "@3sixty/entity-storage-models": "next", + "@3sixty/nameof": "next", + "@3sixty/web": "next" + }, + "devDependencies": { + "@3sixty/entity-storage-connector-memory": "next", + "@3sixty/nameof-transformer": "next", + "@3sixty/nameof-vitest-plugin": "next", + "@3sixty/ts-to-openapi": "next", + "@3sixty/validate-locales": "next", + "@types/node": "26.6.5", + "@vitest/coverage-v8": "5.0.5", + "concurrently": "10.0.6", + "copyfiles": "2.4.1", + "nodemon": "3.1.14", + "rimraf": "6.1.3", + "typedoc": "0.28.20", + "typedoc-plugin-markdown": "4.14.1", + "typescript": "6.0.3", + "vitest": "5.0.5" + }, + "main": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "types": "./dist/types/index.d.ts", + "import": "./dist/es/index.js", + "default": "./dist/es/index.js" + } + }, + "files": [ + "dist/es", + "dist/types", + "locales", + "docs" + ], + "keywords": [ + "twin", + "trade", + "iota", + "framework", + "blockchain", + "api", + "entity", + "storage", + "persistence", + "database", + "service", + "microservice", + "business-logic" + ], + "bugs": { + "url": "git+https://github.com/3sixtyglobal/api-auth/issues" + }, + "homepage": "https://3sixty.global" +} diff --git a/packages/api-auth-entity-storage-service/src/entities/authenticationAuditEntry.ts b/packages/api-auth-service/src/entities/authenticationAuditEntry.ts similarity index 59% rename from packages/api-auth-entity-storage-service/src/entities/authenticationAuditEntry.ts rename to packages/api-auth-service/src/entities/authenticationAuditEntry.ts index 95e284a..608650e 100644 --- a/packages/api-auth-entity-storage-service/src/entities/authenticationAuditEntry.ts +++ b/packages/api-auth-service/src/entities/authenticationAuditEntry.ts @@ -1,6 +1,6 @@ // Copyright 2026 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import { entity, property } from "@3sixty/entity"; +import { SortDirection, entity, property } from "@3sixty/entity"; /** * Class defining the storage for authentication audit entries. @@ -10,43 +10,63 @@ export class AuthenticationAuditEntry { /** * The unique identifier for the audit entry. */ - @property({ type: "string", isPrimary: true }) + @property({ type: "string", isPrimary: true, maxLength: 255 }) public id!: string; /** * The timestamp of the audit entry in ISO 8601 format. */ - @property({ type: "string", isSecondary: true }) + @property({ + type: "string", + format: "date-time", + isSecondary: true, + indexGroup: [{ name: "actorDate", direction: SortDirection.Descending, index: 1 }] + }) public dateCreated!: string; /** * The audit event that occurred. */ - @property({ type: "string", isSecondary: true }) + @property({ + type: "string", + maxLength: 128, + isSecondary: true + }) public event!: string; /** * The actor identifier, could be e-mail, username, or other unique identifier. */ - @property({ type: "string", isSecondary: true, optional: true }) + @property({ + type: "string", + maxLength: 255, + isSecondary: true, + optional: true, + indexGroup: [{ name: "actorDate", direction: SortDirection.Ascending, index: 0 }] + }) public actorId?: string; /** * The node identifier associated with the audit entry, if applicable. */ - @property({ type: "string", isSecondary: true, optional: true }) + @property({ type: "string", maxLength: 255, isSecondary: true, optional: true }) public nodeId?: string; /** * The organization identifier associated with the audit entry, if applicable. */ - @property({ type: "string", isSecondary: true, optional: true }) + @property({ type: "string", maxLength: 255, isSecondary: true, optional: true }) public organizationId?: string; /** * The tenant identifier associated with the audit entry, if applicable. */ - @property({ type: "string", isSecondary: true, optional: true }) + @property({ + type: "string", + maxLength: 255, + isSecondary: true, + optional: true + }) public tenantId?: string; /** @@ -58,13 +78,13 @@ export class AuthenticationAuditEntry { /** * The user agent string of the client. */ - @property({ type: "string", optional: true }) + @property({ type: "string", maxLength: 1024, optional: true }) public userAgent?: string; /** * The correlation ID for request tracing. */ - @property({ type: "string", optional: true }) + @property({ type: "string", maxLength: 255, optional: true }) public correlationId?: string; /** diff --git a/packages/api-auth-entity-storage-service/src/entities/authenticationRateEntry.ts b/packages/api-auth-service/src/entities/authenticationRateEntry.ts similarity index 82% rename from packages/api-auth-entity-storage-service/src/entities/authenticationRateEntry.ts rename to packages/api-auth-service/src/entities/authenticationRateEntry.ts index df36feb..544310f 100644 --- a/packages/api-auth-entity-storage-service/src/entities/authenticationRateEntry.ts +++ b/packages/api-auth-service/src/entities/authenticationRateEntry.ts @@ -10,7 +10,7 @@ export class AuthenticationRateEntry { /** * The id for the rate entry. */ - @property({ type: "string", isPrimary: true }) + @property({ type: "string", isPrimary: true, maxLength: 255 }) public id!: string; /** @@ -22,6 +22,6 @@ export class AuthenticationRateEntry { /** * Last modification time in ISO date format. */ - @property({ type: "string" }) + @property({ type: "string", format: "date-time" }) public dateModified!: string; } diff --git a/packages/api-auth-service/src/index.ts b/packages/api-auth-service/src/index.ts new file mode 100644 index 0000000..c60362c --- /dev/null +++ b/packages/api-auth-service/src/index.ts @@ -0,0 +1,16 @@ +// Copyright 2024 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +export * from "./entities/authenticationAuditEntry.js"; +export * from "./entities/authenticationRateEntry.js"; +export * from "./models/IEntityStorageAuthenticationAuditServiceConfig.js"; +export * from "./models/IEntityStorageAuthenticationAuditServiceConstructorOptions.js"; +export * from "./models/IEntityStorageAuthenticationRateServiceConfig.js"; +export * from "./models/IEntityStorageAuthenticationRateServiceConstructorOptions.js"; +export * from "./models/ITenantOverrideProcessorConfig.js"; +export * from "./models/ITenantOverrideProcessorConstructorOptions.js"; +export * from "./processors/tenantOverrideProcessor.js"; +export * from "./restEntryPoints.js"; +export * from "./routes/entityStorageAuthenticationAuditRoutes.js"; +export * from "./schema.js"; +export * from "./services/entityStorageAuthenticationAuditService.js"; +export * from "./services/entityStorageAuthenticationRateService.js"; diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAuditServiceConfig.ts b/packages/api-auth-service/src/models/IEntityStorageAuthenticationAuditServiceConfig.ts similarity index 100% rename from packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAuditServiceConfig.ts rename to packages/api-auth-service/src/models/IEntityStorageAuthenticationAuditServiceConfig.ts diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.ts b/packages/api-auth-service/src/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.ts similarity index 100% rename from packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.ts rename to packages/api-auth-service/src/models/IEntityStorageAuthenticationAuditServiceConstructorOptions.ts diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationRateServiceConfig.ts b/packages/api-auth-service/src/models/IEntityStorageAuthenticationRateServiceConfig.ts similarity index 100% rename from packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationRateServiceConfig.ts rename to packages/api-auth-service/src/models/IEntityStorageAuthenticationRateServiceConfig.ts diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationRateServiceConstructorOptions.ts b/packages/api-auth-service/src/models/IEntityStorageAuthenticationRateServiceConstructorOptions.ts similarity index 100% rename from packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationRateServiceConstructorOptions.ts rename to packages/api-auth-service/src/models/IEntityStorageAuthenticationRateServiceConstructorOptions.ts diff --git a/packages/api-auth-service/src/models/ITenantOverrideProcessorConfig.ts b/packages/api-auth-service/src/models/ITenantOverrideProcessorConfig.ts new file mode 100644 index 0000000..8d559a1 --- /dev/null +++ b/packages/api-auth-service/src/models/ITenantOverrideProcessorConfig.ts @@ -0,0 +1,24 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +/** + * Configuration for the TenantOverrideProcessor. + */ +export interface ITenantOverrideProcessorConfig { + /** + * Include the stack with errors. + */ + includeErrorStack?: boolean; + + /** + * The role value that grants escalated privilege for cross-tenant access. + * @default global-admin + */ + escalatedPrivilegeRole?: string; + + /** + * The authorization model ID to use for REST requests. + * @default system + */ + authorizationModelId?: string; +} diff --git a/packages/api-auth-service/src/models/ITenantOverrideProcessorConstructorOptions.ts b/packages/api-auth-service/src/models/ITenantOverrideProcessorConstructorOptions.ts new file mode 100644 index 0000000..e6fafb7 --- /dev/null +++ b/packages/api-auth-service/src/models/ITenantOverrideProcessorConstructorOptions.ts @@ -0,0 +1,25 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import type { ITenantOverrideProcessorConfig } from "./ITenantOverrideProcessorConfig.js"; + +/** + * Options for the TenantOverrideProcessor constructor. + */ +export interface ITenantOverrideProcessorConstructorOptions { + /** + * The tenant admin component type. + * @default tenant-admin + */ + tenantAdminComponentType?: string; + + /** + * The authorization component type. + * @default authorization + */ + authorizationComponentType?: string; + + /** + * Configuration for the processor. + */ + config?: ITenantOverrideProcessorConfig; +} diff --git a/packages/api-auth-service/src/processors/tenantOverrideProcessor.ts b/packages/api-auth-service/src/processors/tenantOverrideProcessor.ts new file mode 100644 index 0000000..14b098f --- /dev/null +++ b/packages/api-auth-service/src/processors/tenantOverrideProcessor.ts @@ -0,0 +1,204 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { + ForbiddenError, + HttpContextIdKeys, + HttpErrorHelper, + type IBaseRoute, + type IBaseRouteProcessor, + type IHttpResponse, + type IHttpServerRequest, + type ITenant, + type ITenantAdminComponent +} from "@3sixty/api-models"; +import type { IAuthorizationComponent } from "@3sixty/authorization-models"; +import { ContextIdKeys, type IContextIds } from "@3sixty/context"; +import { BaseError, ComponentFactory, GuardError, Is, NotFoundError } from "@3sixty/core"; +import { nameof } from "@3sixty/nameof"; +import { HttpStatusCode } from "@3sixty/web"; +import type { ITenantOverrideProcessorConstructorOptions } from "../models/ITenantOverrideProcessorConstructorOptions.js"; + +/** + * Processes the overrideTenant query parameter for routes that allow tenant override. + * Requires the caller to hold the escalated privilege role. Runs after AuthHeaderProcessor so + * authentication is always resolved in the caller's own partition first. + */ +export class TenantOverrideProcessor implements IBaseRouteProcessor { + /** + * The query parameter name used to supply the override tenant ID. + */ + public static readonly OVERRIDE_TENANT_PARAM: string = "override-tenant"; + + /** + * The model identifier that is reserved for system-only use. + */ + public static readonly DEFAULT_AUTHORIZATION_MODEL_ID: string = "system"; + + /** + * The role string required to perform a tenant override. + */ + public static readonly DEFAULT_ESCALATED_PRIVILEGE_ROLE: string = "global-admin"; + + /** + * Runtime name for the class. + */ + public static readonly CLASS_NAME: string = nameof(); + + /** + * The component used to resolve tenants. + * @internal + */ + private readonly _tenantAdminComponent: ITenantAdminComponent; + + /** + * The authorization component for checking authorization roles. + * @internal + */ + private readonly _authorizationComponent: IAuthorizationComponent; + + /** + * Include the stack with errors. + * @internal + */ + private readonly _includeErrorStack: boolean; + + /** + * The role value that grants cross-tenant access. + * @internal + */ + private readonly _escalatedPrivilegeRole: string; + + /** + * The authorization model ID to use for REST requests. + * @internal + */ + private readonly _authorizationModelId: string; + + /** + * Create a new instance of TenantOverrideProcessor. + * @param options Options for the processor. + */ + constructor(options?: ITenantOverrideProcessorConstructorOptions) { + this._tenantAdminComponent = ComponentFactory.get( + options?.tenantAdminComponentType ?? "tenant-admin" + ); + this._authorizationComponent = ComponentFactory.get( + options?.authorizationComponentType ?? "authorization" + ); + this._includeErrorStack = options?.config?.includeErrorStack ?? false; + this._escalatedPrivilegeRole = + options?.config?.escalatedPrivilegeRole ?? + TenantOverrideProcessor.DEFAULT_ESCALATED_PRIVILEGE_ROLE; + this._authorizationModelId = + options?.config?.authorizationModelId ?? + TenantOverrideProcessor.DEFAULT_AUTHORIZATION_MODEL_ID; + } + + /** + * Returns the class name of the component. + * @returns The class name of the component. + */ + public className(): string { + return TenantOverrideProcessor.CLASS_NAME; + } + + /** + * Pre process the REST request for the specified route. + * @param request The incoming request. + * @param response The outgoing response. + * @param route The route to process. + * @param contextIds The context IDs of the request. + * @param processorState The state handed through the processors. + * @returns A promise that resolves when the tenant override has been applied or skipped. + */ + public async pre( + request: IHttpServerRequest, + response: IHttpResponse, + route: IBaseRoute | undefined, + contextIds: IContextIds, + processorState: { [id: string]: unknown } + ): Promise { + if (Is.empty(route) || (route.disableTenantOverride ?? false)) { + return; + } + + const overrideTenantParam = request.query?.[TenantOverrideProcessor.OVERRIDE_TENANT_PARAM]; + if (!Is.stringValue(overrideTenantParam)) { + return; + } + + // If a prior processor already set an error response, skip processing. + if (Is.notEmpty(response.statusCode) && response.statusCode >= HttpStatusCode.badRequest) { + return; + } + + try { + const userId = contextIds[ContextIdKeys.User]; + if (!Is.stringValue(userId)) { + throw new ForbiddenError( + TenantOverrideProcessor.CLASS_NAME, + "missingUserIdForTenantOverride" + ); + } + + // Role must be directly assigned to the user, not inherited through a group or other means. + const hasRole = await this._authorizationComponent.hasRoleForSubject( + this._authorizationModelId, + userId, + this._escalatedPrivilegeRole + ); + + // Caller must hold the escalated privilege role. + if (!hasRole) { + throw new ForbiddenError( + TenantOverrideProcessor.CLASS_NAME, + "insufficientRoleForTenantOverride" + ); + } + + // Verify the tenant exists before substituting. + await this.verifyTenantExists(overrideTenantParam); + + contextIds[HttpContextIdKeys.OriginalTenant] = contextIds[ContextIdKeys.Tenant]; + contextIds[ContextIdKeys.Tenant] = overrideTenantParam; + } catch (err) { + const error = BaseError.fromError(err); + const { httpStatusCode } = HttpErrorHelper.processError(error); + HttpErrorHelper.buildResponse(response, error, httpStatusCode, this._includeErrorStack); + } + } + + /** + * Confirm the tenant being switched to exists. + * @param tenantId The tenant id from the override query parameter. + * @throws NotFoundError if no tenant matches the id. + * @internal + */ + private async verifyTenantExists(tenantId: string): Promise { + let tenant: ITenant; + + try { + tenant = await this._tenantAdminComponent.get(tenantId); + } catch (err) { + // An id that matches no tenant, or is not even well formed, is a not found; anything + // else is a genuine fault and keeps its own status code. + if ( + BaseError.isErrorName(err, NotFoundError.CLASS_NAME) || + BaseError.isErrorName(err, GuardError.CLASS_NAME) + ) { + throw new NotFoundError( + TenantOverrideProcessor.CLASS_NAME, + "tenantNotFound", + tenantId, + undefined, + err + ); + } + throw err; + } + + if (tenant.id !== tenantId) { + throw new NotFoundError(TenantOverrideProcessor.CLASS_NAME, "tenantNotFound", tenantId); + } + } +} diff --git a/packages/api-auth-service/src/restEntryPoints.ts b/packages/api-auth-service/src/restEntryPoints.ts new file mode 100644 index 0000000..591814a --- /dev/null +++ b/packages/api-auth-service/src/restEntryPoints.ts @@ -0,0 +1,19 @@ +// Copyright 2024 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import type { IRestRouteEntryPoint } from "@3sixty/api-models"; +import { + generateRestRoutesAuthenticationAudit, + tagsAuthenticationAudit +} from "./routes/entityStorageAuthenticationAuditRoutes.js"; + +/** + * REST entry points for the authentication audit service. + */ +export const restEntryPoints: IRestRouteEntryPoint[] = [ + { + name: "authenticationAudit", + defaultBaseRoute: "authentication/audit", + tags: tagsAuthenticationAudit, + generateRoutes: generateRestRoutesAuthenticationAudit + } +]; diff --git a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAuditRoutes.ts b/packages/api-auth-service/src/routes/entityStorageAuthenticationAuditRoutes.ts similarity index 94% rename from packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAuditRoutes.ts rename to packages/api-auth-service/src/routes/entityStorageAuthenticationAuditRoutes.ts index 132dcf6..739c8e2 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAuditRoutes.ts +++ b/packages/api-auth-service/src/routes/entityStorageAuthenticationAuditRoutes.ts @@ -9,7 +9,7 @@ import type { IAuditRemoveRequest, IAuditUpdateRequest, IAuthenticationAuditComponent -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { HttpContextIdKeys, HttpHeaderHelper, @@ -18,6 +18,7 @@ import { type IHttpRequestContext, type INoContentResponse, type IRestRoute, + type IRouteAuthorization, type ITag, type IUnauthorizedResponse } from "@3sixty/api-models"; @@ -31,6 +32,19 @@ import { HeaderTypes, HttpStatusCode, type IHttpHeaders } from "@3sixty/web"; */ const ROUTES_SOURCE = "authenticationAuditRoutes"; +/** + * The default authorization for the routes, used to seed authorization rules. + */ +const DEFAULT_AUTHORIZATION_READER: IRouteAuthorization = { + permission: "user-admin:read", + role: "user-admin" +}; +const DEFAULT_AUTHORIZATION_WRITER: IRouteAuthorization = { + permission: "user-admin:write", + role: "user-admin", + inherits: [DEFAULT_AUTHORIZATION_READER.permission] +}; + /** * The tag to associate with the routes. */ @@ -96,7 +110,8 @@ export function generateRestRoutesAuthenticationAudit( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const getRoute: IRestRoute = { @@ -145,7 +160,8 @@ export function generateRestRoutesAuthenticationAudit( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_READER }; const updateRoute: IRestRoute = { @@ -182,7 +198,8 @@ export function generateRestRoutesAuthenticationAudit( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const removeRoute: IRestRoute = { @@ -214,7 +231,8 @@ export function generateRestRoutesAuthenticationAudit( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_WRITER }; const queryRoute: IRestRoute = { @@ -272,7 +290,8 @@ export function generateRestRoutesAuthenticationAudit( { type: nameof() } - ] + ], + defaultAuthorization: DEFAULT_AUTHORIZATION_READER }; return [createRoute, getRoute, updateRoute, removeRoute, queryRoute]; diff --git a/packages/api-auth-service/src/schema.ts b/packages/api-auth-service/src/schema.ts new file mode 100644 index 0000000..f06ab2e --- /dev/null +++ b/packages/api-auth-service/src/schema.ts @@ -0,0 +1,18 @@ +// Copyright 2024 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity"; +import { nameof } from "@3sixty/nameof"; +import { AuthenticationAuditEntry } from "./entities/authenticationAuditEntry.js"; +import { AuthenticationRateEntry } from "./entities/authenticationRateEntry.js"; + +/** + * Initialize the schema for the authentication audit and rate services. + */ +export function initSchema(): void { + EntitySchemaFactory.register(nameof(), () => + EntitySchemaHelper.getSchema(AuthenticationAuditEntry) + ); + EntitySchemaFactory.register(nameof(), () => + EntitySchemaHelper.getSchema(AuthenticationRateEntry) + ); +} diff --git a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAuditService.ts b/packages/api-auth-service/src/services/entityStorageAuthenticationAuditService.ts similarity index 99% rename from packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAuditService.ts rename to packages/api-auth-service/src/services/entityStorageAuthenticationAuditService.ts index 54f4678..e15157d 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAuditService.ts +++ b/packages/api-auth-service/src/services/entityStorageAuthenticationAuditService.ts @@ -4,7 +4,7 @@ import type { AuthAuditEvent, IAuthenticationAuditComponent, IAuthenticationAuditEntry -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { HttpContextIdKeys } from "@3sixty/api-models"; import { ContextIdStore, ContextIdKeys } from "@3sixty/context"; import { diff --git a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationRateService.ts b/packages/api-auth-service/src/services/entityStorageAuthenticationRateService.ts similarity index 99% rename from packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationRateService.ts rename to packages/api-auth-service/src/services/entityStorageAuthenticationRateService.ts index bb95d37..9e1c5dd 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationRateService.ts +++ b/packages/api-auth-service/src/services/entityStorageAuthenticationRateService.ts @@ -3,7 +3,7 @@ import type { IAuthenticationRateActionConfig, IAuthenticationRateComponent -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { TooManyRequestsError, type IPlatformComponent } from "@3sixty/api-models"; import type { ITaskSchedulerComponent } from "@3sixty/background-task-models"; import { ComponentFactory, Converter, GeneralError, Guards, Is } from "@3sixty/core"; diff --git a/packages/api-auth-service/tests/processors/tenantOverrideProcessor.spec.ts b/packages/api-auth-service/tests/processors/tenantOverrideProcessor.spec.ts new file mode 100644 index 0000000..e9a00c4 --- /dev/null +++ b/packages/api-auth-service/tests/processors/tenantOverrideProcessor.spec.ts @@ -0,0 +1,363 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { + ForbiddenError, + HttpContextIdKeys, + type IHttpResponse, + type IHttpServerRequest, + type ITenantAdminComponent +} from "@3sixty/api-models"; +import type { IAuthorizationComponent } from "@3sixty/authorization-models"; +import { ContextIdKeys, type IContextIds } from "@3sixty/context"; +import { ComponentFactory, GeneralError, GuardError, NotFoundError } from "@3sixty/core"; +import { HttpStatusCode } from "@3sixty/web"; +import { TenantOverrideProcessor } from "../../src/processors/tenantOverrideProcessor.js"; + +const CALLER_TENANT = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"; +const CALLER_USER = "user-001"; +const OVERRIDE_TENANT = "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7"; +const INVALID_TENANT = "not-a-valid-tenant-id"; + +describe("TenantOverrideProcessor", () => { + let mockTenantAdminComponent: ITenantAdminComponent; + let mockAuthorizationComponent: IAuthorizationComponent; + // Untyped reference so mockResolvedValue(undefined) needs no cast. + let getTenantMock: ReturnType; + let processor: TenantOverrideProcessor; + + beforeEach(() => { + vi.restoreAllMocks(); + + getTenantMock = vi.fn().mockResolvedValue({ id: OVERRIDE_TENANT }); + + mockTenantAdminComponent = { + get: getTenantMock + } as unknown as ITenantAdminComponent; + + mockAuthorizationComponent = { + hasRoleForSubject: vi.fn().mockResolvedValue(true) + } as unknown as IAuthorizationComponent; + + vi.spyOn(ComponentFactory, "get").mockImplementation(type => { + if (type === "tenant-admin") { + return mockTenantAdminComponent as never; + } + return mockAuthorizationComponent as never; + }); + + processor = new TenantOverrideProcessor(); + }); + + it("should return the class name", () => { + expect(processor.className()).toBe(TenantOverrideProcessor.CLASS_NAME); + }); + + it("should skip processing when route is undefined", async () => { + const contextIds: IContextIds = { [ContextIdKeys.Tenant]: CALLER_TENANT }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + undefined, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should skip processing when route explicitly disables tenant override", async () => { + const contextIds: IContextIds = { [ContextIdKeys.Tenant]: CALLER_TENANT }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "someRoute", path: "/", disableTenantOverride: true }, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should skip processing when override-tenant query param is absent", async () => { + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: {} } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should skip processing when response already has a status code set", async () => { + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = { statusCode: HttpStatusCode.unauthorized }; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + expect(mockTenantAdminComponent.get).not.toHaveBeenCalled(); + }); + + it("should proceed with override when response already has a 2xx status code", async () => { + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = { statusCode: HttpStatusCode.ok }; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(contextIds[ContextIdKeys.Tenant]).toBe(OVERRIDE_TENANT); + expect(contextIds[HttpContextIdKeys.OriginalTenant]).toBe(CALLER_TENANT); + expect(mockTenantAdminComponent.get).toHaveBeenCalledWith(OVERRIDE_TENANT); + }); + + it("should return 403 when caller user ID is not set in context", async () => { + const contextIds: IContextIds = { [ContextIdKeys.Tenant]: CALLER_TENANT }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.forbidden); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + expect(mockAuthorizationComponent.hasRoleForSubject).not.toHaveBeenCalled(); + }); + + it("should return 403 when override-tenant is supplied but caller lacks escalated privilege", async () => { + vi.mocked(mockAuthorizationComponent.hasRoleForSubject).mockResolvedValue(false); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.forbidden); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + expect(mockTenantAdminComponent.get).not.toHaveBeenCalled(); + }); + + it("should return 404 when the override tenant id is not well formed", async () => { + getTenantMock.mockRejectedValue( + new GuardError("tenantAdminService", "guard.stringHexLength", "tenantId", INVALID_TENANT) + ); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": INVALID_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.notFound); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should return 404 when override-tenant does not exist in the system", async () => { + getTenantMock.mockRejectedValue( + new NotFoundError("tenantAdminService", "tenantNotFound", OVERRIDE_TENANT) + ); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.notFound); + expect((response.body as { message: string }).message).toBe( + "tenantOverrideProcessor.tenantNotFound" + ); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should return 404 when the component returns a tenant with a different id", async () => { + getTenantMock.mockResolvedValue({ id: CALLER_TENANT }); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.notFound); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should pass a component fault through rather than reporting it as a missing tenant", async () => { + getTenantMock.mockRejectedValue(new GeneralError("tenantAdminService", "storageUnavailable")); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.internalServerError); + expect(contextIds[ContextIdKeys.Tenant]).toBe(CALLER_TENANT); + }); + + it("should substitute the tenant partition and store the original when override is valid", async () => { + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Tenant]).toBe(OVERRIDE_TENANT); + expect(contextIds[HttpContextIdKeys.OriginalTenant]).toBe(CALLER_TENANT); + expect(mockTenantAdminComponent.get).toHaveBeenCalledWith(OVERRIDE_TENANT); + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledWith( + "system", + CALLER_USER, + TenantOverrideProcessor.DEFAULT_ESCALATED_PRIVILEGE_ROLE + ); + }); + + it("should respond with 403 body containing the error name", async () => { + vi.mocked(mockAuthorizationComponent.hasRoleForSubject).mockResolvedValue(false); + + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await processor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBe(HttpStatusCode.forbidden); + const body = response.body as { name: string }; + expect(body.name).toBe(ForbiddenError.CLASS_NAME); + }); + + it("should accept a custom escalated privilege role when configured", async () => { + vi.mocked(mockAuthorizationComponent.hasRoleForSubject).mockResolvedValue(true); + + const customProcessor = new TenantOverrideProcessor({ + config: { escalatedPrivilegeRole: "super-admin" } + }); + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await customProcessor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Tenant]).toBe(OVERRIDE_TENANT); + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledWith( + "system", + CALLER_USER, + "super-admin" + ); + }); + + it("should use a custom authorizationModelId when configured", async () => { + const customProcessor = new TenantOverrideProcessor({ + config: { authorizationModelId: "custom-model" } + }); + const contextIds: IContextIds = { + [ContextIdKeys.Tenant]: CALLER_TENANT, + [ContextIdKeys.User]: CALLER_USER + }; + const response: IHttpResponse = {}; + await customProcessor.pre( + { query: { "override-tenant": OVERRIDE_TENANT } } as unknown as IHttpServerRequest, + response, + { operationId: "tenantGetById", path: "/" }, + contextIds, + {} + ); + + expect(response.statusCode).toBeUndefined(); + expect(mockAuthorizationComponent.hasRoleForSubject).toHaveBeenCalledWith( + "custom-model", + CALLER_USER, + TenantOverrideProcessor.DEFAULT_ESCALATED_PRIVILEGE_ROLE + ); + }); +}); diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts b/packages/api-auth-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts similarity index 99% rename from packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts rename to packages/api-auth-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts index ef6329f..a9a27b2 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts +++ b/packages/api-auth-service/tests/routes/entityStorageAuthenticationAuditRoutes.spec.ts @@ -3,7 +3,7 @@ import type { IAuthenticationAuditComponent, IAuthenticationAuditEntry -} from "@3sixty/api-auth-entity-storage-models"; +} from "@3sixty/api-auth-models"; import { ComponentFactory } from "@3sixty/core"; import { HeaderTypes, HttpStatusCode } from "@3sixty/web"; import { diff --git a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAuditService.spec.ts b/packages/api-auth-service/tests/services/entityStorageAuthenticationAuditService.spec.ts similarity index 100% rename from packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationAuditService.spec.ts rename to packages/api-auth-service/tests/services/entityStorageAuthenticationAuditService.spec.ts diff --git a/packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationRateService.spec.ts b/packages/api-auth-service/tests/services/entityStorageAuthenticationRateService.spec.ts similarity index 100% rename from packages/api-auth-entity-storage-service/tests/services/entityStorageAuthenticationRateService.spec.ts rename to packages/api-auth-service/tests/services/entityStorageAuthenticationRateService.spec.ts diff --git a/packages/api-auth-service/tests/tsconfig.json b/packages/api-auth-service/tests/tsconfig.json new file mode 100644 index 0000000..8f550b2 --- /dev/null +++ b/packages/api-auth-service/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "../.cache/tests.tsbuildinfo", + "rootDir": "..", + "types": ["vitest/globals"] + }, + "include": ["./**/*.ts"] +} diff --git a/packages/api-auth-service/ts-to-openapi.json b/packages/api-auth-service/ts-to-openapi.json new file mode 100644 index 0000000..15fd224 --- /dev/null +++ b/packages/api-auth-service/ts-to-openapi.json @@ -0,0 +1,14 @@ +{ + "title": "3Sixty - Test Endpoints", + "version": "1.0.0", + "description": "REST API for 3Sixty - Test Endpoints.", + "licenseName": "Apache 2.0 License", + "licenseUrl": "https://opensource.org/licenses/Apache-2.0", + "servers": ["https://localhost"], + "authMethods": ["jwtBearer"], + "restRoutes": [ + { + "packageRoot": "./" + } + ] +} diff --git a/packages/api-auth-service/tsconfig.json b/packages/api-auth-service/tsconfig.json new file mode 100644 index 0000000..a8a4cec --- /dev/null +++ b/packages/api-auth-service/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo", + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "ES2022", + "rootDir": "./src", + "outDir": "dist/es", + "strict": true, + "forceConsistentCasingInFileNames": true, + "stripInternal": true, + "noEmitHelpers": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "erasableSyntaxOnly": true, + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "declarationDir": "dist/types", + "types": ["node"], + "plugins": [{ "transform": "@3sixty/nameof-transformer" }], + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/api-auth-service/typedoc.config.mjs b/packages/api-auth-service/typedoc.config.mjs new file mode 100644 index 0000000..aad5b59 --- /dev/null +++ b/packages/api-auth-service/typedoc.config.mjs @@ -0,0 +1,24 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { OptionDefaults } from 'typedoc'; + +const customSchemaTags = ['@json-ld', '@json-schema']; + +export default { + entryPoints: ['src/index.ts'], + out: './docs/reference', + hideGenerator: true, + hideBreadcrumbs: true, + hidePageHeader: true, + excludeExternals: true, + githubPages: false, + disableSources: true, + sort: ['source-order'], + treatWarningsAsErrors: true, + readme: 'none', + entryFileName: 'index', + plugin: ['typedoc-plugin-markdown'], + blockTags: [...new Set([...OptionDefaults.blockTags, ...customSchemaTags])], + excludeTags: customSchemaTags, + useCustomAnchors: true +}; diff --git a/packages/api-auth-service/vitest.config.ts b/packages/api-auth-service/vitest.config.ts new file mode 100644 index 0000000..d40698f --- /dev/null +++ b/packages/api-auth-service/vitest.config.ts @@ -0,0 +1,24 @@ +// Copyright 2025 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import { NameOfPlugin } from "@3sixty/nameof-vitest-plugin"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [NameOfPlugin], + test: { + include: ["./tests/**/*.spec.ts"], + globals: true, + cache: false, + testTimeout: 300000, + hookTimeout: 300000, + bail: 1, + reporters: ["verbose"], + disableConsoleIntercept: true, + coverage: { + reporter: ["text", "lcov"], + include: ["src/**/*.ts"], + exclude: ["**/index.ts", "**/models/**/*.ts"] + }, + fileParallelism: false + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77e38f2..0321f9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -298,10 +298,10 @@ importers: version: link:../api-auth-entity-storage-models '@3sixty/api-core': specifier: next - version: 0.20.0-next.0(supports-color@10.2.2) + version: 0.20.0-next.1(supports-color@10.2.2) '@3sixty/api-models': specifier: next - version: 0.20.0-next.0 + version: 0.20.0-next.1 '@3sixty/core': specifier: next version: 0.20.0-next.2 @@ -357,15 +357,210 @@ importers: '@3sixty/api-auth-entity-storage-models': specifier: 0.20.0-next.0 version: link:../api-auth-entity-storage-models + '@3sixty/api-auth-models': + specifier: 0.20.0-next.0 + version: link:../api-auth-models + '@3sixty/api-models': + specifier: next + version: 0.20.0-next.1 + '@3sixty/authorization-models': + specifier: next + version: 0.20.0-next.1 + '@3sixty/context': + specifier: next + version: 0.20.0-next.2 + '@3sixty/core': + specifier: next + version: 0.20.0-next.2 + '@3sixty/crypto': + specifier: next + version: 0.20.0-next.2 + '@3sixty/entity': + specifier: next + version: 0.20.0-next.2 + '@3sixty/entity-storage-models': + specifier: next + version: 0.20.0-next.1 + '@3sixty/nameof': + specifier: next + version: 0.20.0-next.2 + '@3sixty/vault-models': + specifier: next + version: 0.20.0-next.1 + '@3sixty/web': + specifier: next + version: 0.20.0-next.2 + devDependencies: + '@3sixty/entity-storage-connector-memory': + specifier: next + version: 0.20.0-next.1 + '@3sixty/nameof-transformer': + specifier: next + version: 0.20.0-next.2 + '@3sixty/nameof-vitest-plugin': + specifier: next + version: 0.20.0-next.2(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + '@3sixty/ts-to-openapi': + specifier: next + version: 0.20.0-next.4 + '@3sixty/validate-locales': + specifier: next + version: 0.20.0-next.2 + '@types/node': + specifier: 26.6.5 + version: 26.6.5 + '@vitest/coverage-v8': + specifier: 5.0.5 + version: 5.0.5(vitest@5.0.5) + concurrently: + specifier: 10.0.6 + version: 10.0.6 + copyfiles: + specifier: 2.4.1 + version: 2.4.1 + nodemon: + specifier: 3.1.14 + version: 3.1.14 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + typedoc: + specifier: 0.28.20 + version: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: + specifier: 4.14.1 + version: 4.14.1(typedoc@0.28.20(typescript@6.0.3)) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: 5.0.5 + version: 5.0.5(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + + packages/api-auth-models: + dependencies: + '@3sixty/core': + specifier: next + version: 0.20.0-next.2 + '@3sixty/nameof': + specifier: next + version: 0.20.0-next.2 + devDependencies: + '@3sixty/nameof-transformer': + specifier: next + version: 0.20.0-next.2 + '@3sixty/nameof-vitest-plugin': + specifier: next + version: 0.20.0-next.2(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + '@3sixty/validate-locales': + specifier: next + version: 0.20.0-next.2 + '@types/node': + specifier: 26.6.5 + version: 26.6.5 + '@vitest/coverage-v8': + specifier: 5.0.5 + version: 5.0.5(vitest@5.0.5) + concurrently: + specifier: 10.0.6 + version: 10.0.6 + copyfiles: + specifier: 2.4.1 + version: 2.4.1 + nodemon: + specifier: 3.1.14 + version: 3.1.14 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + typedoc: + specifier: 0.28.20 + version: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: + specifier: 4.14.1 + version: 4.14.1(typedoc@0.28.20(typescript@6.0.3)) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: 5.0.5 + version: 5.0.5(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + + packages/api-auth-rest-client: + dependencies: + '@3sixty/api-auth-models': + specifier: 0.20.0-next.0 + version: link:../api-auth-models '@3sixty/api-core': specifier: next - version: 0.20.0-next.0(supports-color@10.2.2) + version: 0.20.0-next.1(supports-color@10.2.2) + '@3sixty/api-models': + specifier: next + version: 0.20.0-next.1 + '@3sixty/core': + specifier: next + version: 0.20.0-next.2 + '@3sixty/nameof': + specifier: next + version: 0.20.0-next.2 + '@3sixty/web': + specifier: next + version: 0.20.0-next.2 + devDependencies: + '@3sixty/nameof-transformer': + specifier: next + version: 0.20.0-next.2 + '@3sixty/nameof-vitest-plugin': + specifier: next + version: 0.20.0-next.2(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + '@3sixty/validate-locales': + specifier: next + version: 0.20.0-next.2 + '@types/node': + specifier: 26.6.5 + version: 26.6.5 + '@vitest/coverage-v8': + specifier: 5.0.5 + version: 5.0.5(vitest@5.0.5) + concurrently: + specifier: 10.0.6 + version: 10.0.6 + copyfiles: + specifier: 2.4.1 + version: 2.4.1 + nodemon: + specifier: 3.1.14 + version: 3.1.14 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + typedoc: + specifier: 0.28.20 + version: 0.28.20(typescript@6.0.3) + typedoc-plugin-markdown: + specifier: 4.14.1 + version: 4.14.1(typedoc@0.28.20(typescript@6.0.3)) + typescript: + specifier: 6.0.3 + version: 6.0.3 + vitest: + specifier: 5.0.5 + version: 5.0.5(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)) + + packages/api-auth-service: + dependencies: + '@3sixty/api-auth-models': + specifier: 0.20.0-next.0 + version: link:../api-auth-models '@3sixty/api-models': specifier: next - version: 0.20.0-next.0 + version: 0.20.0-next.1 + '@3sixty/authorization-models': + specifier: next + version: 0.20.0-next.1 '@3sixty/background-task-models': specifier: next - version: 0.20.0-next.0 + version: 0.20.0-next.1 '@3sixty/context': specifier: next version: 0.20.0-next.2 @@ -380,23 +575,17 @@ importers: version: 0.20.0-next.2 '@3sixty/entity-storage-models': specifier: next - version: 0.20.0-next.0 - '@3sixty/logging-models': - specifier: next - version: 0.20.0-next.0 + version: 0.20.0-next.1 '@3sixty/nameof': specifier: next version: 0.20.0-next.2 - '@3sixty/vault-models': - specifier: next - version: 0.20.0-next.0 '@3sixty/web': specifier: next version: 0.20.0-next.2 devDependencies: '@3sixty/entity-storage-connector-memory': specifier: next - version: 0.20.0-next.0 + version: 0.20.0-next.1 '@3sixty/nameof-transformer': specifier: next version: 0.20.0-next.2 @@ -442,16 +631,20 @@ importers: packages: - '@3sixty/api-core@0.20.0-next.0': - resolution: {integrity: sha512-tPyKJpr7Odcaa99U1dfCvy0UUiV7sZH7rBsFBJ5obXdlSXd0/aC6Sh2iFJpMShkr0NLTB7v4PhKE75BDvz/Qyw==} + '@3sixty/api-core@0.20.0-next.1': + resolution: {integrity: sha512-gd6IJEufB6C7Sjv3G+BRPKP7UiZ1NTSUdJqD2nD5JSU9mJ41PwhkZ1PArV5PODUXUI8A/f93g463WxTlYf8Vgw==} engines: {node: '>=24.0.0'} - '@3sixty/api-models@0.20.0-next.0': - resolution: {integrity: sha512-AShxxmk3JR5dvLyC3zXYsBb4FZbtoNYsRlB2TUynLk4epOSZcZN+p8Ya+cMM+vsbLT8joA/Jk8TC9fPSEKSk3g==} + '@3sixty/api-models@0.20.0-next.1': + resolution: {integrity: sha512-1KmZs1JOKJKqo1UEpIgWblBcnefBQ/L027HPcck7gqYp/pDJT93/OOPPv0VwHccDY4C/UN0ZhC1oD4TPv+2k3Q==} engines: {node: '>=24.0.0'} - '@3sixty/background-task-models@0.20.0-next.0': - resolution: {integrity: sha512-CDLBSjZaTb81FFCV5OscA5G4gEAQb5zaKK4H9BcQiJnTf1k8U1BGeU+O5U+H9PM1v3Trm3h7eEqFhMbXKLonIQ==} + '@3sixty/authorization-models@0.20.0-next.1': + resolution: {integrity: sha512-t8MsVzRm6AmlNUtENJLym7bW0zR8dwqZ920gErKRFGknbNH4eoh5fmxLH+4zA1EOmgohn//xSKP8UR9uxdRHNA==} + engines: {node: '>=24.0.0'} + + '@3sixty/background-task-models@0.20.0-next.1': + resolution: {integrity: sha512-GUfVa5aIINeKCU7FOFiFj433BlkY5LF+mNN6ZrlPCGFE2n68bzHL8romVw1RJxb6ocVIXgt7nJ0XWMO2+ALHXA==} engines: {node: '>=24.0.0'} '@3sixty/cli-core@0.20.0-next.2': @@ -470,20 +663,20 @@ packages: resolution: {integrity: sha512-0tLDaAs5j3NDob9PcmTGP7SBBr2BUnmmgj7/EuvGZNXB1qDV9+ZlYbI2R6KSf8FtGs+S1VsOohgRoqIIBoGvgQ==} engines: {node: '>=24.0.0'} - '@3sixty/entity-storage-connector-memory@0.20.0-next.0': - resolution: {integrity: sha512-jN76Ljx3Xmqx6sng1aBR6WE66mcxUxEwWON5CzBjBRq21PvJdS7jFmXz6JPJH387dRzy5ar76W9yenrES5ajrQ==} + '@3sixty/entity-storage-connector-memory@0.20.0-next.1': + resolution: {integrity: sha512-IEDnCJGk/DlH3jHajIG+s5SQ36ZhR0Mjo38ISXJKmXbQlcl4CtNfh3Ac/1isBt7cbL2FU4ZeJGOL1pVFAa4xlQ==} engines: {node: '>=24.0.0'} - '@3sixty/entity-storage-models@0.20.0-next.0': - resolution: {integrity: sha512-IG5JgBzpNScXF2+XExjFYLnSSbsFfYGCERXMx6JpB1NRzD7XvwmUWUkvBxGjcXhayEqnE0eEiEdXRp7Xevt3Iw==} + '@3sixty/entity-storage-models@0.20.0-next.1': + resolution: {integrity: sha512-Ruf3cG2VOipevsG1WnZjxya/MIu0M4XNaEwZ7O/inbje+vkdf0PHDyHqxcFXMXOTX+U8dvD+z7SPitfsBp/Tvg==} engines: {node: '>=24.0.0'} '@3sixty/entity@0.20.0-next.2': resolution: {integrity: sha512-iq8T0sVI+FGheo7SXmoEPtUAgSIvej7NSy06vD3od5a84WyHzTHsf4nlvV97VDegFyUxXjAhKS5WjLK8tbjFdA==} engines: {node: '>=24.0.0'} - '@3sixty/logging-models@0.20.0-next.0': - resolution: {integrity: sha512-I4EunSdIs/pHPAM0uGw9T8KDWluGFtWJ5n+jYCW9Vwgk/OF4Vszila/IdSt0NN2kVYzGj4sCD6CvPtJOcT+FXw==} + '@3sixty/logging-models@0.20.0-next.1': + resolution: {integrity: sha512-PvrnZSBuws7TML96voAcKN7jZqMSbMKyq3IXh3dZ1zpgU6U7iBsqT5ynfOln2P3vkVdD29ohFOrX1zr5NPsX8Q==} engines: {node: '>=24.0.0'} '@3sixty/nameof-transformer@0.20.0-next.2': @@ -499,6 +692,10 @@ packages: resolution: {integrity: sha512-isPi+GgUWa/B3+gtLbPm3HJ5yaMJrp2uMlzLIkC2aBGQGC2jFmDk5HSmbpJg7mhQf38YomwI6AzC2JaZcL1XWQ==} engines: {node: '>=24.0.0'} + '@3sixty/telemetry-models@0.20.0-next.1': + resolution: {integrity: sha512-yqK3nWrqe4GCmo+mmLXytI+FWVm0d8CPIV9tw5OAtOVe9y6d7qKU63CR00mZGI6V/E/VTJmCv+Rm3ABRMVvwHQ==} + engines: {node: '>=24.0.0'} + '@3sixty/tools-core@0.20.0-next.4': resolution: {integrity: sha512-M/NKlmAvQoOn/ailU8yhjjINjP4nEqR3YG0qiYauxhyMOwQJLqcfXoVCo4zzAURcBcb+SY3YuYR07dG3VAsqTg==} engines: {node: '>=24.0.0'} @@ -517,8 +714,8 @@ packages: engines: {node: '>=24.0.0'} hasBin: true - '@3sixty/vault-models@0.20.0-next.0': - resolution: {integrity: sha512-6XQPJ+IKkiue0xTLCD2VtZP+HjokVEklkZuMYiOTFmFDGX1H5Ai+Qx3F4jO9jXvfP5bNjce4b5byhuPKC49N0A==} + '@3sixty/vault-models@0.20.0-next.1': + resolution: {integrity: sha512-w3rNruwfhaiNpkIopMuD3Yn/5vTyfCWntJ2sJu/xT5nrUyqGm3GkgvbkTZeF7q9Q3nuvQRt3nWkdSjUM3cZoTw==} engines: {node: '>=24.0.0'} '@3sixty/web@0.20.0-next.2': @@ -3233,8 +3430,8 @@ packages: resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} engines: {node: '>= 18'} - socket.io-client@4.8.3: - resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} + socket.io-client@4.8.4: + resolution: {integrity: sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==} engines: {node: '>=10.0.0'} socket.io-parser@4.2.7: @@ -3686,19 +3883,19 @@ packages: snapshots: - '@3sixty/api-core@0.20.0-next.0(supports-color@10.2.2)': + '@3sixty/api-core@0.20.0-next.1(supports-color@10.2.2)': dependencies: - '@3sixty/api-models': 0.20.0-next.0 + '@3sixty/api-models': 0.20.0-next.1 '@3sixty/core': 0.20.0-next.2 '@3sixty/nameof': 0.20.0-next.2 '@3sixty/web': 0.20.0-next.2 - socket.io-client: 4.8.3(supports-color@10.2.2) + socket.io-client: 4.8.4(supports-color@10.2.2) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@3sixty/api-models@0.20.0-next.0': + '@3sixty/api-models@0.20.0-next.1': dependencies: '@3sixty/context': 0.20.0-next.2 '@3sixty/core': 0.20.0-next.2 @@ -3706,7 +3903,13 @@ snapshots: '@3sixty/nameof': 0.20.0-next.2 '@3sixty/web': 0.20.0-next.2 - '@3sixty/background-task-models@0.20.0-next.0': + '@3sixty/authorization-models@0.20.0-next.1': + dependencies: + '@3sixty/api-models': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.2 + '@3sixty/telemetry-models': 0.20.0-next.1 + + '@3sixty/background-task-models@0.20.0-next.1': dependencies: '@3sixty/core': 0.20.0-next.2 '@3sixty/entity': 0.20.0-next.2 @@ -3743,23 +3946,23 @@ snapshots: '@scure/bip39': 2.4.0 micro-key-producer: 0.10.2 - '@3sixty/entity-storage-connector-memory@0.20.0-next.0': + '@3sixty/entity-storage-connector-memory@0.20.0-next.1': dependencies: - '@3sixty/api-models': 0.20.0-next.0 + '@3sixty/api-models': 0.20.0-next.1 '@3sixty/context': 0.20.0-next.2 '@3sixty/core': 0.20.0-next.2 '@3sixty/entity': 0.20.0-next.2 - '@3sixty/entity-storage-models': 0.20.0-next.0 - '@3sixty/logging-models': 0.20.0-next.0 + '@3sixty/entity-storage-models': 0.20.0-next.1 + '@3sixty/logging-models': 0.20.0-next.1 '@3sixty/nameof': 0.20.0-next.2 - '@3sixty/entity-storage-models@0.20.0-next.0': + '@3sixty/entity-storage-models@0.20.0-next.1': dependencies: '@3sixty/context': 0.20.0-next.2 '@3sixty/core': 0.20.0-next.2 '@3sixty/crypto': 0.20.0-next.2 '@3sixty/entity': 0.20.0-next.2 - '@3sixty/logging-models': 0.20.0-next.0 + '@3sixty/logging-models': 0.20.0-next.1 '@3sixty/nameof': 0.20.0-next.2 '@3sixty/entity@0.20.0-next.2': @@ -3769,7 +3972,7 @@ snapshots: reflect-metadata: 0.2.2 tslib: 2.8.1 - '@3sixty/logging-models@0.20.0-next.0': + '@3sixty/logging-models@0.20.0-next.1': dependencies: '@3sixty/core': 0.20.0-next.2 '@3sixty/entity': 0.20.0-next.2 @@ -3801,6 +4004,12 @@ snapshots: '@3sixty/nameof@0.20.0-next.2': {} + '@3sixty/telemetry-models@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.2 + '@3sixty/entity': 0.20.0-next.2 + '@3sixty/nameof': 0.20.0-next.2 + '@3sixty/tools-core@0.20.0-next.4': dependencies: '@3sixty/core': 0.20.0-next.2 @@ -3836,7 +4045,7 @@ snapshots: glob: 13.0.6 typescript: 6.0.3 - '@3sixty/vault-models@0.20.0-next.0': + '@3sixty/vault-models@0.20.0-next.1': dependencies: '@3sixty/core': 0.20.0-next.2 '@3sixty/nameof': 0.20.0-next.2 @@ -6619,7 +6828,7 @@ snapshots: smol-toml@1.9.1: {} - socket.io-client@4.8.3(supports-color@10.2.2): + socket.io-client@4.8.4(supports-color@10.2.2): dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3(supports-color@10.2.2) diff --git a/release/release-please-config.prerelease.json b/release/release-please-config.prerelease.json index 1b0042b..86ede0e 100644 --- a/release/release-please-config.prerelease.json +++ b/release/release-please-config.prerelease.json @@ -11,6 +11,18 @@ "prerelease": true, "prerelease-type": "next", "packages": { + "packages/api-auth-models": { + "package-name": "api-auth-models", + "changelog-path": "docs/changelog.md" + }, + "packages/api-auth-service": { + "package-name": "api-auth-service", + "changelog-path": "docs/changelog.md" + }, + "packages/api-auth-rest-client": { + "package-name": "api-auth-rest-client", + "changelog-path": "docs/changelog.md" + }, "packages/api-auth-entity-storage-models": { "package-name": "api-auth-entity-storage-models", "changelog-path": "docs/changelog.md" @@ -30,6 +42,9 @@ "type": "linked-versions", "groupName": "repo", "components": [ + "api-auth-models", + "api-auth-service", + "api-auth-rest-client", "api-auth-entity-storage-models", "api-auth-entity-storage-service", "api-auth-entity-storage-rest-client" diff --git a/release/release-please-config.prod.json b/release/release-please-config.prod.json index 12eb380..44f1c53 100644 --- a/release/release-please-config.prod.json +++ b/release/release-please-config.prod.json @@ -9,6 +9,18 @@ "initial-version": "0.0.1", "versioning": "default", "packages": { + "packages/api-auth-models": { + "package-name": "api-auth-models", + "changelog-path": "docs/changelog.md" + }, + "packages/api-auth-service": { + "package-name": "api-auth-service", + "changelog-path": "docs/changelog.md" + }, + "packages/api-auth-rest-client": { + "package-name": "api-auth-rest-client", + "changelog-path": "docs/changelog.md" + }, "packages/api-auth-entity-storage-models": { "package-name": "api-auth-entity-storage-models", "changelog-path": "docs/changelog.md" @@ -28,6 +40,9 @@ "type": "linked-versions", "groupName": "repo", "components": [ + "api-auth-models", + "api-auth-service", + "api-auth-rest-client", "api-auth-entity-storage-models", "api-auth-entity-storage-service", "api-auth-entity-storage-rest-client" diff --git a/release/release-please-manifest.prerelease.json b/release/release-please-manifest.prerelease.json index 3ebe731..19473f7 100644 --- a/release/release-please-manifest.prerelease.json +++ b/release/release-please-manifest.prerelease.json @@ -1,4 +1,7 @@ { + "packages/api-auth-models": "0.20.0-next.0", + "packages/api-auth-service": "0.20.0-next.0", + "packages/api-auth-rest-client": "0.20.0-next.0", "packages/api-auth-entity-storage-models": "0.20.0-next.0", "packages/api-auth-entity-storage-service": "0.20.0-next.0", "packages/api-auth-entity-storage-rest-client": "0.20.0-next.0" diff --git a/release/release-please-manifest.prod.json b/release/release-please-manifest.prod.json index 2a1d64b..0cadd4e 100644 --- a/release/release-please-manifest.prod.json +++ b/release/release-please-manifest.prod.json @@ -1,5 +1,8 @@ { - "packages/api-auth-entity-storage-models": "0.9.1", - "packages/api-auth-entity-storage-service": "0.9.1", - "packages/api-auth-entity-storage-rest-client": "0.9.1" + "packages/api-auth-models": "0.9.2", + "packages/api-auth-service": "0.9.2", + "packages/api-auth-rest-client": "0.9.2", + "packages/api-auth-entity-storage-models": "0.9.2", + "packages/api-auth-entity-storage-service": "0.9.2", + "packages/api-auth-entity-storage-rest-client": "0.9.2" }