From a45f751f298d7c23627379c135c13cd9f1db1b8f Mon Sep 17 00:00:00 2001 From: Adrian Sanchez Date: Thu, 15 Oct 2026 13:28:43 +0200 Subject: [PATCH 1/8] feat: let an account omit its DID until after sign-in --- .../src/index.ts | 1 + .../models/IAuthenticationAdminComponent.ts | 3 +- .../src/models/IAuthenticationComponent.ts | 9 ++ .../src/models/api/IAdminUserCreateRequest.ts | 4 +- .../models/api/IAuthenticationUserCreate.ts | 33 +++++ ...ityStorageAuthenticationAdminRestClient.ts | 5 +- .../entityStorageAuthenticationRestClient.ts | 19 +++ .../locales/en.json | 4 + ...AuthenticationServiceConstructorOptions.ts | 6 + .../src/models/IIdentityAdoption.ts | 35 +++++ .../entityStorageAuthenticationAdminRoutes.ts | 12 ++ .../entityStorageAuthenticationRoutes.ts | 60 ++++++++- ...entityStorageAuthenticationAdminService.ts | 53 +++++--- .../entityStorageAuthenticationService.ts | 88 +++++++++++++ .../entityStorageAuthenticationRoutes.spec.ts | 53 ++++++++ ...yStorageAuthenticationAdminService.spec.ts | 76 +++++++++++ ...entityStorageAuthenticationService.spec.ts | 123 +++++++++++++++++- 17 files changed, 559 insertions(+), 25 deletions(-) create mode 100644 packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts create mode 100644 packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts create mode 100644 packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts diff --git a/packages/api-auth-entity-storage-models/src/index.ts b/packages/api-auth-entity-storage-models/src/index.ts index ff0bf13..1225f58 100644 --- a/packages/api-auth-entity-storage-models/src/index.ts +++ b/packages/api-auth-entity-storage-models/src/index.ts @@ -1,6 +1,7 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. export * from "./models/api/IAdminUserCreateRequest.js"; +export * from "./models/api/IAuthenticationUserCreate.js"; export * from "./models/api/IAdminUserGetByIdentityRequest.js"; export * from "./models/api/IAdminUserGetRequest.js"; export * from "./models/api/IAdminUserGetResponse.js"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts index 004e3e1..7a3a4bd 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts @@ -1,6 +1,7 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; +import type { IAuthenticationUserCreate } from "./api/IAuthenticationUserCreate.js"; import type { IAuthenticationUser } from "./IAuthenticationUser.js"; /** @@ -12,7 +13,7 @@ export interface IAuthenticationAdminComponent extends IComponent { * @param user The user to create. * @returns A promise that resolves when the user account has been created. */ - create(user: IAuthenticationUser & { password: string }): Promise; + create(user: IAuthenticationUserCreate): Promise; /** * Update a login for the user. 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..b20e7f7 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts @@ -20,6 +20,15 @@ export interface IAuthenticationComponent extends IComponent { expiry: number; }>; + /** + * Replace an e-mail subject with a self-controlled DID and issue a new token. + * @returns The new token and its expiry. The route places the token on the response cookie. + */ + adoptIdentity(): Promise<{ + token?: string; + expiry: number; + }>; + /** * Logout the current user. * @param token The token to logout, if it uses a mechanism with public access. diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts index a2dd314..f26a88f 100644 --- a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts +++ b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts @@ -1,6 +1,6 @@ // Copyright 2026 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import type { IAuthenticationUser } from "../IAuthenticationUser.js"; +import type { IAuthenticationUserCreate } from "./IAuthenticationUserCreate.js"; /** * Create a new user as an admin. @@ -9,5 +9,5 @@ export interface IAdminUserCreateRequest { /** * The body of the request. */ - body: IAuthenticationUser & { password: string }; + body: IAuthenticationUserCreate; } diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts b/packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts new file mode 100644 index 0000000..ac94cf7 --- /dev/null +++ b/packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts @@ -0,0 +1,33 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. + +/** + * Fields accepted when an admin creates a user. + * The identity and the organisation may be omitted. The service fills them. + */ +export interface IAuthenticationUserCreate { + /** + * The user e-mail address. + */ + email: string; + + /** + * Password for the new account. + */ + password: string; + + /** + * The scope assigned to the user. + */ + scope: string[]; + + /** + * Identity subject. When omitted, the service stores the e-mail address until a DID exists. + */ + userIdentity?: string; + + /** + * Organisation identity. When omitted, the service uses the organisation on the request context. + */ + organizationIdentity?: string; +} diff --git a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts index fa2734d..2bbdc71 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts @@ -9,7 +9,8 @@ import type { IAdminUserUpdatePasswordRequest, IAdminUserUpdateRequest, IAuthenticationAdminComponent, - IAuthenticationUser + IAuthenticationUser, + IAuthenticationUserCreate } from "@3sixty/api-auth-entity-storage-models"; import { BaseRestClient } from "@3sixty/api-core"; import type { IBaseRestClientConfig, INoContentResponse } from "@3sixty/api-models"; @@ -50,7 +51,7 @@ export class EntityStorageAuthenticationAdminRestClient * @param user The user to create. * @returns Nothing. */ - public async create(user: IAuthenticationUser & { password: string }): Promise { + public async create(user: IAuthenticationUserCreate): Promise { Guards.object(EntityStorageAuthenticationAdminRestClient.CLASS_NAME, nameof(user), user); Guards.stringValue( EntityStorageAuthenticationAdminRestClient.CLASS_NAME, 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..c4152ca 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts @@ -94,6 +94,25 @@ export class EntityStorageAuthenticationRestClient }; } + /** + * Replace an e-mail subject with a self-controlled DID and issue a new token. + * @returns The new token and its expiry. The token is read from the response cookie. + */ + public async adoptIdentity(): Promise<{ + token?: string; + expiry: number; + }> { + const response = await this.fetch("/identity", "POST"); + + return { + token: CookieHelper.getCookieFromHeaders( + response?.headers?.[HeaderTypes.SetCookie], + this._cookieName + ), + expiry: response.body.expiry + }; + } + /** * Logout the current user. * @param token The token to logout, if it uses a mechanism with public access. diff --git a/packages/api-auth-entity-storage-service/locales/en.json b/packages/api-auth-entity-storage-service/locales/en.json index eceae72..5f0a974 100644 --- a/packages/api-auth-entity-storage-service/locales/en.json +++ b/packages/api-auth-entity-storage-service/locales/en.json @@ -3,6 +3,10 @@ "entityStorageAuthenticationService": { "loginFailed": "Login failed", "userNotFound": "The user with the specified e-mail could not be found", + "notAuthenticated": "The request has no signed-in user", + "identityAlreadyAssigned": "The user already has a DID", + "identityComponentMissing": "The identity component is not available", + "identityCreateFailed": "Creating the DID failed", "passwordMismatch": "The password does not match the user's password", "nodeIdNotSet": "The node ID has not been set" }, diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts index fd62e8c..88b1361 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts @@ -36,6 +36,12 @@ export interface IEntityStorageAuthenticationServiceConstructorOptions { */ tenantAdminComponentType?: string; + /** + * The identity component used to create a self-controlled DID. + * @default identity + */ + identityComponentType?: string; + /** * The configuration for the authentication. */ diff --git a/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts b/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts new file mode 100644 index 0000000..d2cbb83 --- /dev/null +++ b/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts @@ -0,0 +1,35 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import type { IComponent } from "@3sixty/core"; + +/** + * The identity operations this service needs in order to attach a DID. + * The identity package owns the full component. This is the slice used here. + */ +export interface IIdentityAdoption extends IComponent { + /** + * Create a document controlled by the new DID. + * @returns The new document. Only the id is used. + */ + identityCreateSelf(): Promise<{ + /** + * The DID of the new document. + */ + id: string; + }>; + + /** + * Remove a document. + * @param identity The document id to remove. + * @param options Optional removal settings. + * @param options.removeKeys Also remove associated private keys from the vault. + * @param options.removeDocumentKey Also remove the document key from the vault. + * @param controller The controller allowed to remove the document. + * @returns A promise that resolves when the document has been removed. + */ + identityRemove( + identity: string, + options?: { removeKeys?: boolean; removeDocumentKey?: boolean }, + controller?: string + ): Promise; +} 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..73dc07b 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts @@ -74,6 +74,18 @@ export function generateRestRoutesAuthenticationAdmin( scope: ["scope1", "scope2"] } } + }, + { + id: "createUserWithoutIdentityRequestExample", + description: + "The request to create a user before a DID exists. The service stores the e-mail as the subject and the context organisation as the organisation.", + request: { + body: { + email: "user@example.com", + password: "MyPassword123!", + scope: [] + } + } } ] }, 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..355d19d 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -10,6 +10,8 @@ import type { IUpdatePasswordRequest } from "@3sixty/api-auth-entity-storage-models"; import type { + IConflictResponse, + IHttpRequest, IHttpRequestContext, INoContentResponse, IRestRoute, @@ -196,7 +198,39 @@ export function generateRestRoutesAuthentication( ] }; - return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute]; + const adoptIdentityRoute: IRestRoute = { + operationId: "authenticationAdoptIdentity", + summary: "Adopt a DID for the signed-in user", + tag: tagsAuthentication[0].name, + method: "POST", + path: `${baseRouteName}/identity`, + handler: async httpRequestContext => + authenticationAdoptIdentity(httpRequestContext, componentName), + responseType: [ + { + type: nameof(), + examples: [ + { + id: "adoptIdentityResponseExample", + description: "The new session expiry. The token is set as a cookie.", + response: { + body: { + expiry: 1722514341067 + } + } + } + ] + }, + { + type: nameof() + }, + { + type: nameof() + } + ] + }; + + return [loginRoute, logoutRoute, refreshTokenRoute, updatePasswordRoute, adoptIdentityRoute]; } /** @@ -229,6 +263,30 @@ export async function authenticationLogin( }; } +/** + * Adopt a DID for the signed-in user and reissue the session cookie. + * @param httpRequestContext The request context for the API. + * @param componentName The name of the component to use in the routes. + * @returns The response object with the new expiry. The token is not included in the body. + */ +export async function authenticationAdoptIdentity( + httpRequestContext: IHttpRequestContext, + componentName: string +): Promise { + const component = ComponentFactory.get(componentName); + const result = await component.adoptIdentity(); + + httpRequestContext.processorState.authOperation = "login"; + httpRequestContext.processorState.authToken = result.token; + httpRequestContext.processorState.authTokenLocation = "cookie"; + + return { + body: { + expiry: result.expiry + } + }; +} + /** * Logout from the server. * @param httpRequestContext The request context for the API. 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..ac1cd51 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -3,7 +3,8 @@ import type { IAuthenticationAdminComponent, IAuthenticationAuditComponent, - IAuthenticationUser + IAuthenticationUser, + IAuthenticationUserCreate } from "@3sixty/api-auth-entity-storage-models"; import { AuthAuditEvent } from "@3sixty/api-auth-entity-storage-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; @@ -11,6 +12,7 @@ import { ComponentFactory, Converter, GeneralError, + GuardError, Guards, Is, NotFoundError, @@ -79,11 +81,12 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA /** * Create a login for the user. + * A missing identity is stored as the lowercased e-mail. A missing organisation is taken from the request context. * @param user The user to create. * @returns A promise that resolves when the user account has been created and the audit entry recorded. */ - public async create(user: IAuthenticationUser & { password: string }): Promise { - Guards.object( + public async create(user: IAuthenticationUserCreate): Promise { + Guards.object( EntityStorageAuthenticationAdminService.CLASS_NAME, nameof(user), user @@ -98,22 +101,37 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA nameof(user.password), user.password ); - Guards.stringValue( - EntityStorageAuthenticationAdminService.CLASS_NAME, - nameof(user.userIdentity), - user.userIdentity - ); - Guards.stringValue( - EntityStorageAuthenticationAdminService.CLASS_NAME, - nameof(user.organizationIdentity), - user.organizationIdentity - ); Guards.array( EntityStorageAuthenticationAdminService.CLASS_NAME, nameof(user.scope), user.scope ); + const userIdentity = + user.userIdentity === undefined ? user.email.trim().toLowerCase() : user.userIdentity; + if (!Is.stringValue(userIdentity)) { + throw new GuardError( + EntityStorageAuthenticationAdminService.CLASS_NAME, + "guard.stringEmpty", + nameof(user.userIdentity), + user.userIdentity + ); + } + + const contextIds = await ContextIdStore.getContextIds(); + const organizationIdentity = + user.organizationIdentity === undefined + ? contextIds?.[ContextIdKeys.Organization] + : user.organizationIdentity; + if (!Is.stringValue(organizationIdentity)) { + throw new GuardError( + EntityStorageAuthenticationAdminService.CLASS_NAME, + "guard.stringEmpty", + nameof(user.organizationIdentity), + user.organizationIdentity + ); + } + try { PasswordValidator.validatePassword(user.password, { minLength: this._minPasswordLength @@ -133,22 +151,21 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA email: user.email, salt: Converter.bytesToBase64(saltBytes), password: hashedPassword, - identity: user.userIdentity, - organization: user.organizationIdentity, + identity: userIdentity, + organization: organizationIdentity, scope: user.scope.map(s => s.trim().toLocaleLowerCase()).join(","), passwordVersion: 0 }; await this._userEntityStorage.set(newUser); - const contextIds = await ContextIdStore.getContextIds(); const requestorTenantId = contextIds?.[ContextIdKeys.Tenant]; await this._authenticationAuditService?.create({ actorId: user.email, event: AuthAuditEvent.AccountCreated, data: { - userIdentity: user.userIdentity, - organizationIdentity: user.organizationIdentity, + userIdentity, + organizationIdentity, tenantId: requestorTenantId, scope: user.scope } 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..7e0546b 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -12,6 +12,7 @@ import { ContextIdHelper, ContextIdKeys, ContextIdStore } from "@3sixty/context" import { Coerce, ComponentFactory, + ConflictError, Converter, GeneralError, Guards, @@ -28,6 +29,7 @@ import { nameof } from "@3sixty/nameof"; import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import type { AuthenticationUser } from "../entities/authenticationUser.js"; import type { IEntityStorageAuthenticationServiceConstructorOptions } from "../models/IEntityStorageAuthenticationServiceConstructorOptions.js"; +import type { IIdentityAdoption } from "../models/IIdentityAdoption.js"; import { PasswordHelper } from "../utils/passwordHelper.js"; import { TokenHelper } from "../utils/tokenHelper.js"; @@ -148,6 +150,12 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon */ private readonly _tenantAdminComponent?: ITenantAdminComponent; + /** + * The identity component used to create a self-controlled DID. + * @internal + */ + private readonly _identityComponent?: IIdentityAdoption; + /** * The node identity. * @internal @@ -177,6 +185,10 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon options?.tenantAdminComponentType ?? "tenant-admin" ); + this._identityComponent = ComponentFactory.getIfExists( + options?.identityComponentType ?? "identity" + ); + this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing"; this._defaultTtlMinutes = options?.config?.defaultTtlMinutes ?? EntityStorageAuthenticationService._DEFAULT_TTL_MINUTES; @@ -336,6 +348,82 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon return tokenAndExpiry; } + /** + * Replace an e-mail subject with a self-controlled DID and issue a new token. + * @returns The new token and its expiry. The route places the token on the response cookie. + * @throws UnauthorizedError When no signed-in user can be loaded. + * @throws ConflictError When the user already has a DID. + * @throws GeneralError When the node or the identity component is not available, or the new document id is not a DID. + */ + public async adoptIdentity(): Promise<{ + token?: string; + expiry: number; + }> { + if (!Is.stringValue(this._nodeId)) { + throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet"); + } + + const contextIds = await ContextIdStore.getContextIds(); + const subject = contextIds?.[ContextIdKeys.User]; + if (!Is.stringValue(subject)) { + throw new UnauthorizedError( + EntityStorageAuthenticationService.CLASS_NAME, + "notAuthenticated" + ); + } + + const user = await this._userEntityStorage.get(subject, "identity"); + if (!Is.object(user) || user.identity !== subject) { + throw new UnauthorizedError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound"); + } + + if (user.identity.startsWith("did:")) { + throw new ConflictError( + EntityStorageAuthenticationService.CLASS_NAME, + "identityAlreadyAssigned" + ); + } + + const identityComponent = this._identityComponent; + if (!Is.object(identityComponent)) { + throw new GeneralError( + EntityStorageAuthenticationService.CLASS_NAME, + "identityComponentMissing" + ); + } + + const created = await identityComponent.identityCreateSelf(); + const documentId = created.id; + if (!Is.stringValue(documentId) || !documentId.startsWith("did:")) { + throw new GeneralError( + EntityStorageAuthenticationService.CLASS_NAME, + "identityCreateFailed" + ); + } + + try { + await this._userEntityStorage.set({ + ...user, + identity: documentId + }); + } catch (error) { + await identityComponent.identityRemove(documentId, undefined, documentId); + throw error; + } + + return TokenHelper.createToken( + this._vaultConnector, + this._nodeId, + this._signingKeyName, + documentId, + user.organization, + contextIds?.[ContextIdKeys.Tenant], + this._defaultTtlMinutes, + user.scope, + user.passwordVersion ?? 0 + ); + } + /** * Logout the current user. * @param token The token to logout, if it uses a mechanism with public access. diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts new file mode 100644 index 0000000..e36fc90 --- /dev/null +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -0,0 +1,53 @@ +// Copyright 2026 IOTA Stiftung. +// SPDX-License-Identifier: Apache-2.0. +import type { IHttpRequestContext, IHttpResponse } from "@3sixty/api-models"; +import { ComponentFactory } from "@3sixty/core"; +import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models"; +import { VaultConnectorFactory } from "@3sixty/vault-models"; +import { HeaderTypes } from "@3sixty/web"; +import { AuthHeaderProcessor } from "../../src/processors/authHeaderProcessor.js"; +import { authenticationAdoptIdentity } from "../../src/routes/entityStorageAuthenticationRoutes.js"; + +describe("authenticationAdoptIdentity", () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.spyOn(EntityStorageConnectorFactory, "get").mockReturnValue({} as never); + vi.spyOn(VaultConnectorFactory, "get").mockReturnValue({} as never); + vi.spyOn(ComponentFactory, "getIfExists").mockReturnValue(undefined); + }); + + it("should set the new token as a cookie and leave it out of the body", async () => { + vi.spyOn(ComponentFactory, "get").mockReturnValue({ + adoptIdentity: vi.fn().mockResolvedValue({ + token: "new-jwt-token", + expiry: 1722514341067 + }) + }); + const httpRequestContext = { + processorState: { + authTokenLocation: "authorization" + } + } as IHttpRequestContext; + + const result = await authenticationAdoptIdentity(httpRequestContext, "authentication"); + const response: IHttpResponse = { + body: result.body + }; + const processor = new AuthHeaderProcessor(); + await processor.post( + { headers: {} } as never, + response, + {} as never, + {}, + httpRequestContext.processorState + ); + + expect(result.body).toEqual({ expiry: 1722514341067 }); + expect(result.body).not.toHaveProperty("token"); + expect(JSON.stringify(result.body)).not.toContain("new-jwt-token"); + expect(httpRequestContext.processorState.authOperation).toBe("login"); + expect(httpRequestContext.processorState.authToken).toBe("new-jwt-token"); + expect(httpRequestContext.processorState.authTokenLocation).not.toBe("authorization"); + expect(response.headers?.[HeaderTypes.SetCookie]).toContain("new-jwt-token"); + }); +}); 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..02a5d1d 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 @@ -94,6 +94,82 @@ describe("EntityStorageAuthenticationAdminService", () => { }); }); + it("should store the email and the context organisation when identities are omitted", 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 ContextIdStore.run({ [ContextIdKeys.Organization]: "did:org:node" }, async () => { + await service.create({ + email: "User@Example.com", + password: "correct-horse-battery", + scope: [] + }); + }); + + expect(await userEntityStorage.get("User@Example.com")).toEqual({ + email: "User@Example.com", + salt: "AQIDBA==", + password: "hashed-password", + identity: "user@example.com", + organization: "did:org:node", + scope: "", + passwordVersion: 0 + }); + expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ + actorId: "User@Example.com", + event: "account-created", + data: { + userIdentity: "user@example.com", + organizationIdentity: "did:org:node", + tenantId: undefined, + scope: [] + } + }); + }); + + it("should reject a blank identity and write no user", async () => { + vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); + + await expect( + service.create({ + email: "user@example.com", + password: "correct-horse-battery", + userIdentity: "", + organizationIdentity: "did:org:456", + scope: [] + }) + ).rejects.toThrow(); + expect(await userEntityStorage.get("user@example.com")).toBeUndefined(); + expect(mockAuthenticationAuditService.create).not.toHaveBeenCalled(); + }); + + it("should reject create when no organisation is available and write no user", async () => { + vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); + + await expect( + ContextIdStore.run({}, async () => { + await service.create({ + email: "user@example.com", + password: "correct-horse-battery", + scope: [] + }); + }) + ).rejects.toThrow(); + expect(await userEntityStorage.get("user@example.com")).toBeUndefined(); + const rejection = await service + .create({ + email: "user@example.com", + password: "correct-horse-battery", + scope: [] + }) + .then( + () => "", + (error: Error) => error.message + ); + expect(rejection).not.toContain("correct-horse-battery"); + }); + it("should wrap create failures when the user already exists", async () => { await userEntityStorage.set({ email: "user@example.com", 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..558ae08 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 @@ -6,7 +6,7 @@ import type { } from "@3sixty/api-auth-entity-storage-models"; import { TooManyRequestsError } from "@3sixty/api-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; -import { ComponentFactory, NotFoundError, UnauthorizedError } from "@3sixty/core"; +import { ComponentFactory, ConflictError, NotFoundError, UnauthorizedError } from "@3sixty/core"; import { PasswordGenerator, PasswordValidator } from "@3sixty/crypto"; import { MemoryEntityStorageConnector } from "@3sixty/entity-storage-connector-memory"; import { @@ -953,4 +953,125 @@ describe("EntityStorageAuthenticationService", () => { 0 ); }); + + describe("adoptIdentity", () => { + const mnemonic = "abandon abandon abandon abandon abandon abandon"; + const documentId = "did:iota:tst:abc"; + let identity: { + identityCreateSelf: ReturnType; + identityRemove: ReturnType; + }; + let user: AuthenticationUser; + + beforeEach(async () => { + identity = { + identityCreateSelf: vi.fn().mockResolvedValue({ id: documentId, mnemonic }), + identityRemove: vi.fn().mockResolvedValue(undefined) + }; + user = { + email: "User@Example.com", + identity: "user@example.com", + organization: "did:org:node", + password: "stored-password-hash", + salt: "c2FsdA==", + scope: "", + passwordVersion: 0 + }; + vi.spyOn(ComponentFactory, "getIfExists").mockImplementation(componentName => { + if (componentName === "authentication-audit") { + return mockAuthenticationAuditService; + } + if (componentName === "identity") { + return identity; + } + return undefined; + }); + service = new EntityStorageAuthenticationService(); + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1", + [ContextIdKeys.Tenant]: "tenant-1", + [ContextIdKeys.User]: "user@example.com" + }); + vi.mocked(mockUserEntityStorage.get).mockResolvedValue(user); + vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ + token: "new-jwt-token", + expiry: 1722514341067 + }); + await service.start(); + }); + + it("should replace an email subject with the new DID and issue a token for it", async () => { + const result = await service.adoptIdentity(); + + expect(identity.identityCreateSelf).toHaveBeenCalledTimes(1); + expect(mockUserEntityStorage.set).toHaveBeenCalledWith( + expect.objectContaining({ + email: "User@Example.com", + identity: documentId, + organization: "did:org:node", + scope: "", + passwordVersion: 0 + }) + ); + expect(user.identity).toBe("user@example.com"); + expect(TokenHelper.createToken).toHaveBeenCalledWith( + mockVaultConnector, + "node-1", + "auth-signing", + documentId, + "did:org:node", + "tenant-1", + 60, + "", + 0 + ); + expect(result).toEqual({ token: "new-jwt-token", expiry: 1722514341067 }); + expect(JSON.stringify(result)).not.toContain(mnemonic); + expect(JSON.stringify(vi.mocked(TokenHelper.createToken).mock.calls)).not.toContain(mnemonic); + }); + + it("should reject when the user already has a DID and leave the row unchanged", async () => { + user.identity = "did:iota:tst:existing"; + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1", + [ContextIdKeys.User]: "did:iota:tst:existing" + }); + + await expect(service.adoptIdentity()).rejects.toBeInstanceOf(ConflictError); + expect(identity.identityCreateSelf).not.toHaveBeenCalled(); + expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); + expect(user.identity).toBe("did:iota:tst:existing"); + }); + + it("should leave the stored identity unchanged when identity create throws", async () => { + identity.identityCreateSelf.mockRejectedValue(new Error("publish failed")); + + await expect(service.adoptIdentity()).rejects.toThrow("publish failed"); + expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); + expect(TokenHelper.createToken).not.toHaveBeenCalled(); + expect(user.identity).toBe("user@example.com"); + expect(JSON.stringify(identity.identityCreateSelf.mock.results)).not.toContain("token"); + }); + + it("should remove the new DID with itself as controller when the save throws", async () => { + vi.mocked(mockUserEntityStorage.set).mockRejectedValue(new Error("save failed")); + + await expect(service.adoptIdentity()).rejects.toThrow("save failed"); + expect(identity.identityRemove).toHaveBeenCalledWith(documentId, undefined, documentId); + expect(TokenHelper.createToken).not.toHaveBeenCalled(); + expect(user.identity).toBe("user@example.com"); + }); + + it("should keep a recorded mnemonic out of the failure", async () => { + identity.identityCreateSelf.mockResolvedValue({ id: "not-a-did", mnemonic }); + + const rejection = await service.adoptIdentity().then( + () => "", + (error: Error) => `${error.message} ${JSON.stringify(error)}` + ); + expect(rejection).toContain("identityCreateFailed"); + expect(rejection).not.toContain(mnemonic); + expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); + }); + }); }); From a89f6eeaceba42f3e9cd7ce72d70d0dd2dbb747b Mon Sep 17 00:00:00 2001 From: Adrian Sanchez Date: Thu, 15 Oct 2026 16:30:26 +0200 Subject: [PATCH 2/8] fix: pr reviews #1 --- .../src/index.ts | 2 +- .../models/IAuthenticationAdminComponent.ts | 2 +- .../src/models/IAuthenticationComponent.ts | 4 +- .../{api => }/IAuthenticationUserCreate.ts | 0 .../src/models/api/IAdminUserCreateRequest.ts | 2 +- .../entityStorageAuthenticationRestClient.ts | 6 +- ...ityStorageAuthenticationRestClient.spec.ts | 29 ++++++++ .../locales/en.json | 2 +- ...ntityStorageAuthenticationServiceConfig.ts | 6 ++ ...AuthenticationServiceConstructorOptions.ts | 4 +- .../entityStorageAuthenticationRoutes.ts | 9 +-- ...entityStorageAuthenticationAdminService.ts | 42 +++++------ .../entityStorageAuthenticationService.ts | 73 ++++++++++++++----- .../entityStorageAuthenticationRoutes.spec.ts | 43 +++++++++-- ...yStorageAuthenticationAdminService.spec.ts | 26 +++++++ ...entityStorageAuthenticationService.spec.ts | 28 ++++++- 16 files changed, 215 insertions(+), 63 deletions(-) rename packages/api-auth-entity-storage-models/src/models/{api => }/IAuthenticationUserCreate.ts (100%) diff --git a/packages/api-auth-entity-storage-models/src/index.ts b/packages/api-auth-entity-storage-models/src/index.ts index 1225f58..c13c0fa 100644 --- a/packages/api-auth-entity-storage-models/src/index.ts +++ b/packages/api-auth-entity-storage-models/src/index.ts @@ -1,7 +1,6 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. export * from "./models/api/IAdminUserCreateRequest.js"; -export * from "./models/api/IAuthenticationUserCreate.js"; export * from "./models/api/IAdminUserGetByIdentityRequest.js"; export * from "./models/api/IAdminUserGetRequest.js"; export * from "./models/api/IAdminUserGetResponse.js"; @@ -29,4 +28,5 @@ export * from "./models/IAuthenticationComponent.js"; export * from "./models/IAuthenticationRateActionConfig.js"; export * from "./models/IAuthenticationRateComponent.js"; export * from "./models/IAuthenticationUser.js"; +export * from "./models/IAuthenticationUserCreate.js"; export * from "./models/IAuthenticationUserSecure.js"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts index 7a3a4bd..a8e6f52 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts @@ -1,7 +1,7 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; -import type { IAuthenticationUserCreate } from "./api/IAuthenticationUserCreate.js"; +import type { IAuthenticationUserCreate } from "./IAuthenticationUserCreate.js"; import type { IAuthenticationUser } from "./IAuthenticationUser.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 b20e7f7..a464f79 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts @@ -21,8 +21,8 @@ export interface IAuthenticationComponent extends IComponent { }>; /** - * Replace an e-mail subject with a self-controlled DID and issue a new token. - * @returns The new token and its expiry. The route places the token on the response cookie. + * Replace an e-mail subject with a DID and issue a new token. + * @returns The new token and its expiry. */ adoptIdentity(): Promise<{ token?: string; diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts similarity index 100% rename from packages/api-auth-entity-storage-models/src/models/api/IAuthenticationUserCreate.ts rename to packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts index f26a88f..4960f87 100644 --- a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts +++ b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts @@ -1,6 +1,6 @@ // Copyright 2026 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. -import type { IAuthenticationUserCreate } from "./IAuthenticationUserCreate.js"; +import type { IAuthenticationUserCreate } from "../IAuthenticationUserCreate.js"; /** * Create a new user as an admin. 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 c4152ca..cd82177 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts @@ -10,7 +10,7 @@ import type { 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"; @@ -95,14 +95,14 @@ export class EntityStorageAuthenticationRestClient } /** - * Replace an e-mail subject with a self-controlled DID and issue a new token. + * Replace an e-mail subject with a DID and issue a new token. * @returns The new token and its expiry. The token is read from the response cookie. */ public async adoptIdentity(): Promise<{ token?: string; expiry: number; }> { - const response = await this.fetch("/identity", "POST"); + const response = await this.fetch("/identity", "POST"); return { token: CookieHelper.getCookieFromHeaders( 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..4cdbcf1 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 @@ -154,6 +154,35 @@ describe("EntityStorageAuthenticationRestClient", () => { }); }); + describe("adoptIdentity", () => { + test("sends POST to /identity with no body", async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); + + await client.adoptIdentity(); + + const [url, options] = fetchMock.mock.calls[0]; + expect(url).toBe(`${ENDPOINT}/${PREFIX}/identity`); + expect(options.method).toBe(HttpMethod.POST); + expect(options.body).toBeUndefined(); + }); + + test("returns expiry from the response body", async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); + + const result = await client.adoptIdentity(); + + expect(result.expiry).toBe(1722514341067); + }); + + test("returns token as undefined when no Set-Cookie header is present", async () => { + fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); + + const result = await client.adoptIdentity(); + + expect(result.token).toBeUndefined(); + }); + }); + describe("updatePassword", () => { test("throws when currentPassword is empty", async () => { await expect(client.updatePassword("", "MyNewPassword123!")).rejects.toMatchObject({ diff --git a/packages/api-auth-entity-storage-service/locales/en.json b/packages/api-auth-entity-storage-service/locales/en.json index 5f0a974..608bb97 100644 --- a/packages/api-auth-entity-storage-service/locales/en.json +++ b/packages/api-auth-entity-storage-service/locales/en.json @@ -3,7 +3,6 @@ "entityStorageAuthenticationService": { "loginFailed": "Login failed", "userNotFound": "The user with the specified e-mail could not be found", - "notAuthenticated": "The request has no signed-in user", "identityAlreadyAssigned": "The user already has a DID", "identityComponentMissing": "The identity component is not available", "identityCreateFailed": "Creating the DID failed", @@ -12,6 +11,7 @@ }, "entityStorageAuthenticationAdminService": { "userExists": "The user with the specified e-mail already exists", + "identityInUse": "An account already uses that identity", "createUserFailed": "Creating the user failed", "getUserFailed": "Getting the user failed", "updateUserFailed": "Updating the user failed", 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..c798f13 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts @@ -41,4 +41,10 @@ export interface IEntityStorageAuthenticationServiceConfig { * @default { maxAttempts: 30, windowMinutes: 60 } */ tokenRefreshRateLimit?: IAuthenticationRateActionConfig; + + /** + * Optional override for identity adoption rate limit. + * @default { maxAttempts: 5, windowMinutes: 15 } + */ + identityAdoptRateLimit?: IAuthenticationRateActionConfig; } diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts index 88b1361..6fc4b68 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts @@ -37,8 +37,8 @@ export interface IEntityStorageAuthenticationServiceConstructorOptions { tenantAdminComponentType?: string; /** - * The identity component used to create a self-controlled DID. - * @default identity + * The identity component used to create a DID. + * @default identity-service */ identityComponentType?: string; 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 355d19d..374a722 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -11,8 +11,8 @@ import type { } from "@3sixty/api-auth-entity-storage-models"; import type { IConflictResponse, - IHttpRequest, IHttpRequestContext, + INoContentRequest, INoContentResponse, IRestRoute, IRestRouteResponseOptions, @@ -198,7 +198,7 @@ export function generateRestRoutesAuthentication( ] }; - const adoptIdentityRoute: IRestRoute = { + const adoptIdentityRoute: IRestRoute = { operationId: "authenticationAdoptIdentity", summary: "Adopt a DID for the signed-in user", tag: tagsAuthentication[0].name, @@ -212,7 +212,7 @@ export function generateRestRoutesAuthentication( examples: [ { id: "adoptIdentityResponseExample", - description: "The new session expiry. The token is set as a cookie.", + description: "The new session expiry. The token is placed the same way as login.", response: { body: { expiry: 1722514341067 @@ -264,7 +264,7 @@ export async function authenticationLogin( } /** - * Adopt a DID for the signed-in user and reissue the session cookie. + * Adopt a DID for the signed-in user and reissue the session. * @param httpRequestContext The request context for the API. * @param componentName The name of the component to use in the routes. * @returns The response object with the new expiry. The token is not included in the body. @@ -278,7 +278,6 @@ export async function authenticationAdoptIdentity( httpRequestContext.processorState.authOperation = "login"; httpRequestContext.processorState.authToken = result.token; - httpRequestContext.processorState.authTokenLocation = "cookie"; return { body: { 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 ac1cd51..77c1a06 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -12,7 +12,6 @@ import { ComponentFactory, Converter, GeneralError, - GuardError, Guards, Is, NotFoundError, @@ -107,30 +106,21 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA user.scope ); - const userIdentity = - user.userIdentity === undefined ? user.email.trim().toLowerCase() : user.userIdentity; - if (!Is.stringValue(userIdentity)) { - throw new GuardError( - EntityStorageAuthenticationAdminService.CLASS_NAME, - "guard.stringEmpty", - nameof(user.userIdentity), - user.userIdentity - ); - } + const userIdentity = user.userIdentity ?? user.email.trim().toLowerCase(); + Guards.stringValue( + EntityStorageAuthenticationAdminService.CLASS_NAME, + nameof(user.userIdentity), + userIdentity.trim() + ); const contextIds = await ContextIdStore.getContextIds(); const organizationIdentity = - user.organizationIdentity === undefined - ? contextIds?.[ContextIdKeys.Organization] - : user.organizationIdentity; - if (!Is.stringValue(organizationIdentity)) { - throw new GuardError( - EntityStorageAuthenticationAdminService.CLASS_NAME, - "guard.stringEmpty", - nameof(user.organizationIdentity), - user.organizationIdentity - ); - } + user.organizationIdentity ?? contextIds?.[ContextIdKeys.Organization] ?? ""; + Guards.stringValue( + EntityStorageAuthenticationAdminService.CLASS_NAME, + nameof(user.organizationIdentity), + organizationIdentity.trim() + ); try { PasswordValidator.validatePassword(user.password, { @@ -142,6 +132,14 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists"); } + const existingIdentity = await this._userEntityStorage.get(userIdentity, "identity"); + if (Is.object(existingIdentity)) { + throw new GeneralError( + EntityStorageAuthenticationAdminService.CLASS_NAME, + "identityInUse" + ); + } + const saltBytes = RandomHelper.generate(16); const passwordBytes = Converter.utf8ToBytes(user.password); 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 7e0546b..bad08db 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -84,6 +84,18 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon */ private static readonly _DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES: number = 60; + /** + * Default maximum identity adoption attempts in a rate window. + * @internal + */ + private static readonly _DEFAULT_IDENTITY_ADOPT_RATE_MAX_ATTEMPTS: number = 5; + + /** + * Default identity adoption rate window in minutes. + * @internal + */ + private static readonly _DEFAULT_IDENTITY_ADOPT_RATE_WINDOW_MINUTES: number = 15; + /** * The audit service. * @internal @@ -144,6 +156,12 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon */ private readonly _tokenRefreshRateLimit: IAuthenticationRateActionConfig; + /** + * Rate limit configuration for identity adoption. + * @internal + */ + private readonly _identityAdoptRateLimit: IAuthenticationRateActionConfig; + /** * The component to retrieve tenant information. * @internal @@ -186,7 +204,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon ); this._identityComponent = ComponentFactory.getIfExists( - options?.identityComponentType ?? "identity" + options?.identityComponentType ?? "identity-service" ); this._signingKeyName = options?.config?.signingKeyName ?? "auth-signing"; @@ -217,6 +235,14 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon options?.config?.tokenRefreshRateLimit?.windowMinutes ?? EntityStorageAuthenticationService._DEFAULT_TOKEN_REFRESH_RATE_WINDOW_MINUTES }; + this._identityAdoptRateLimit = { + maxAttempts: + options?.config?.identityAdoptRateLimit?.maxAttempts ?? + EntityStorageAuthenticationService._DEFAULT_IDENTITY_ADOPT_RATE_MAX_ATTEMPTS, + windowMinutes: + options?.config?.identityAdoptRateLimit?.windowMinutes ?? + EntityStorageAuthenticationService._DEFAULT_IDENTITY_ADOPT_RATE_WINDOW_MINUTES + }; } /** @@ -246,6 +272,10 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon "token-refresh", this._tokenRefreshRateLimit ); + await this._authenticationRateService.registerAction( + "identity-adopt", + this._identityAdoptRateLimit + ); } /** @@ -257,6 +287,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon await this._authenticationRateService.unregisterAction("login"); await this._authenticationRateService.unregisterAction("password-change"); await this._authenticationRateService.unregisterAction("token-refresh"); + await this._authenticationRateService.unregisterAction("identity-adopt"); } /** @@ -349,9 +380,9 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon } /** - * Replace an e-mail subject with a self-controlled DID and issue a new token. - * @returns The new token and its expiry. The route places the token on the response cookie. - * @throws UnauthorizedError When no signed-in user can be loaded. + * Replace an e-mail subject with a DID and issue a new token. + * @returns The new token and its expiry. + * @throws NotFoundError When the signed-in user cannot be loaded. * @throws ConflictError When the user already has a DID. * @throws GeneralError When the node or the identity component is not available, or the new document id is not a DID. */ @@ -364,17 +395,16 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon } const contextIds = await ContextIdStore.getContextIds(); - const subject = contextIds?.[ContextIdKeys.User]; - if (!Is.stringValue(subject)) { - throw new UnauthorizedError( - EntityStorageAuthenticationService.CLASS_NAME, - "notAuthenticated" - ); - } + ContextIdHelper.guard(contextIds, ContextIdKeys.User); + const subject = contextIds[ContextIdKeys.User]; const user = await this._userEntityStorage.get(subject, "identity"); if (!Is.object(user) || user.identity !== subject) { - throw new UnauthorizedError(EntityStorageAuthenticationService.CLASS_NAME, "userNotFound"); + throw new NotFoundError( + EntityStorageAuthenticationService.CLASS_NAME, + "userNotFound", + subject + ); } if (user.identity.startsWith("did:")) { @@ -384,6 +414,8 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon ); } + await this._authenticationRateService.check("identity-adopt", subject); + const identityComponent = this._identityComponent; if (!Is.object(identityComponent)) { throw new GeneralError( @@ -395,10 +427,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon const created = await identityComponent.identityCreateSelf(); const documentId = created.id; if (!Is.stringValue(documentId) || !documentId.startsWith("did:")) { - throw new GeneralError( - EntityStorageAuthenticationService.CLASS_NAME, - "identityCreateFailed" - ); + throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "identityCreateFailed"); } try { @@ -407,10 +436,20 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon identity: documentId }); } catch (error) { - await identityComponent.identityRemove(documentId, undefined, documentId); + await identityComponent.identityRemove(documentId, { removeKeys: true }, documentId); throw error; } + await this._authenticationAuditService?.create({ + actorId: documentId, + event: AuthAuditEvent.AccountUpdated, + data: { + previousIdentity: subject, + userIdentity: documentId, + organizationIdentity: user.organization + } + }); + return TokenHelper.createToken( this._vaultConnector, this._nodeId, diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts index e36fc90..f3f546d 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -11,23 +11,25 @@ import { authenticationAdoptIdentity } from "../../src/routes/entityStorageAuthe describe("authenticationAdoptIdentity", () => { beforeEach(() => { vi.restoreAllMocks(); - vi.spyOn(EntityStorageConnectorFactory, "get").mockReturnValue({} as never); - vi.spyOn(VaultConnectorFactory, "get").mockReturnValue({} as never); + vi.spyOn(EntityStorageConnectorFactory, "get").mockReturnValue({}); + vi.spyOn(VaultConnectorFactory, "get").mockReturnValue({}); vi.spyOn(ComponentFactory, "getIfExists").mockReturnValue(undefined); }); - it("should set the new token as a cookie and leave it out of the body", async () => { + it("should leave the token out of the body and out of the cookie for a bearer caller", async () => { vi.spyOn(ComponentFactory, "get").mockReturnValue({ + className: () => "authentication", adoptIdentity: vi.fn().mockResolvedValue({ token: "new-jwt-token", expiry: 1722514341067 }) - }); + } as unknown as ReturnType); const httpRequestContext = { + serverRequest: { headers: {} }, processorState: { authTokenLocation: "authorization" } - } as IHttpRequestContext; + } as unknown as IHttpRequestContext; const result = await authenticationAdoptIdentity(httpRequestContext, "authentication"); const response: IHttpResponse = { @@ -47,7 +49,36 @@ describe("authenticationAdoptIdentity", () => { expect(JSON.stringify(result.body)).not.toContain("new-jwt-token"); expect(httpRequestContext.processorState.authOperation).toBe("login"); expect(httpRequestContext.processorState.authToken).toBe("new-jwt-token"); - expect(httpRequestContext.processorState.authTokenLocation).not.toBe("authorization"); + expect(httpRequestContext.processorState.authTokenLocation).toBe("authorization"); + expect(response.headers?.[HeaderTypes.SetCookie]).toBeUndefined(); + }); + + it("should set the new token as a cookie when the caller did not use a bearer token", async () => { + vi.spyOn(ComponentFactory, "get").mockReturnValue({ + className: () => "authentication", + adoptIdentity: vi.fn().mockResolvedValue({ + token: "new-jwt-token", + expiry: 1722514341067 + }) + } as unknown as ReturnType); + const httpRequestContext = { + serverRequest: { headers: {} }, + processorState: {} + } as unknown as IHttpRequestContext; + + const result = await authenticationAdoptIdentity(httpRequestContext, "authentication"); + const response: IHttpResponse = { + body: result.body + }; + const processor = new AuthHeaderProcessor(); + await processor.post( + { headers: {} } as never, + response, + {} as never, + {}, + httpRequestContext.processorState + ); + expect(response.headers?.[HeaderTypes.SetCookie]).toContain("new-jwt-token"); }); }); 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 02a5d1d..42fa6e1 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 @@ -128,6 +128,32 @@ describe("EntityStorageAuthenticationAdminService", () => { }); }); + it("should reject a second account that would share the derived identity", 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 ContextIdStore.run({ [ContextIdKeys.Organization]: "did:org:node" }, async () => { + await service.create({ + email: "User@Example.com", + password: "correct-horse-battery", + scope: [] + }); + await expect( + service.create({ + email: "user@example.com", + password: "correct-horse-battery", + scope: [] + }) + ).rejects.toThrow(); + }); + + expect(await userEntityStorage.get("user@example.com")).toBeUndefined(); + expect(await userEntityStorage.get("User@Example.com")).toMatchObject({ + identity: "user@example.com" + }); + }); + it("should reject a blank identity and write no user", async () => { vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); 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 558ae08..8cf2ceb 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 @@ -220,6 +220,10 @@ describe("EntityStorageAuthenticationService", () => { maxAttempts: 30, windowMinutes: 60 }); + expect(mockAuthenticationRateService.registerAction).toHaveBeenCalledWith("identity-adopt", { + maxAttempts: 5, + windowMinutes: 15 + }); }); it("should register login rate action on start when configured", async () => { @@ -249,6 +253,7 @@ describe("EntityStorageAuthenticationService", () => { expect(mockAuthenticationRateService.unregisterAction).toHaveBeenCalledWith("login"); expect(mockAuthenticationRateService.unregisterAction).toHaveBeenCalledWith("password-change"); expect(mockAuthenticationRateService.unregisterAction).toHaveBeenCalledWith("token-refresh"); + expect(mockAuthenticationRateService.unregisterAction).toHaveBeenCalledWith("identity-adopt"); }); it("should wrap rate limit check failures during login", async () => { @@ -958,6 +963,7 @@ describe("EntityStorageAuthenticationService", () => { const mnemonic = "abandon abandon abandon abandon abandon abandon"; const documentId = "did:iota:tst:abc"; let identity: { + className: ReturnType; identityCreateSelf: ReturnType; identityRemove: ReturnType; }; @@ -965,6 +971,7 @@ describe("EntityStorageAuthenticationService", () => { beforeEach(async () => { identity = { + className: vi.fn().mockReturnValue("IdentityService"), identityCreateSelf: vi.fn().mockResolvedValue({ id: documentId, mnemonic }), identityRemove: vi.fn().mockResolvedValue(undefined) }; @@ -981,7 +988,7 @@ describe("EntityStorageAuthenticationService", () => { if (componentName === "authentication-audit") { return mockAuthenticationAuditService; } - if (componentName === "identity") { + if (componentName === "identity-service") { return identity; } return undefined; @@ -1026,6 +1033,19 @@ describe("EntityStorageAuthenticationService", () => { 0 ); expect(result).toEqual({ token: "new-jwt-token", expiry: 1722514341067 }); + expect(mockAuthenticationRateService.check).toHaveBeenCalledWith( + "identity-adopt", + "user@example.com" + ); + expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ + actorId: documentId, + event: "account-updated", + data: { + previousIdentity: "user@example.com", + userIdentity: documentId, + organizationIdentity: "did:org:node" + } + }); expect(JSON.stringify(result)).not.toContain(mnemonic); expect(JSON.stringify(vi.mocked(TokenHelper.createToken).mock.calls)).not.toContain(mnemonic); }); @@ -1057,7 +1077,11 @@ describe("EntityStorageAuthenticationService", () => { vi.mocked(mockUserEntityStorage.set).mockRejectedValue(new Error("save failed")); await expect(service.adoptIdentity()).rejects.toThrow("save failed"); - expect(identity.identityRemove).toHaveBeenCalledWith(documentId, undefined, documentId); + expect(identity.identityRemove).toHaveBeenCalledWith( + documentId, + { removeKeys: true }, + documentId + ); expect(TokenHelper.createToken).not.toHaveBeenCalled(); expect(user.identity).toBe("user@example.com"); }); From 5229be42f62aa6dd73cfb9edf5247fc733b9893e Mon Sep 17 00:00:00 2001 From: AdrianMSS-XYZ <118835306+AdrianMSS-XYZ@users.noreply.github.com> Date: Mon, 19 Oct 2026 11:00:29 +0200 Subject: [PATCH 3/8] fix: pr reviews #2 --- .../models/IAuthenticationAdminComponent.ts | 2 +- ...entityStorageAuthenticationAdminService.ts | 5 +- .../entityStorageAuthenticationRoutes.spec.ts | 23 +- ...entityStorageAuthenticationService.spec.ts | 22 +- pnpm-lock.yaml | 1383 +++++++++-------- 5 files changed, 772 insertions(+), 663 deletions(-) diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts index a8e6f52..8951a44 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts @@ -1,8 +1,8 @@ // Copyright 2024 IOTA Stiftung. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; -import type { IAuthenticationUserCreate } from "./IAuthenticationUserCreate.js"; import type { IAuthenticationUser } from "./IAuthenticationUser.js"; +import type { IAuthenticationUserCreate } from "./IAuthenticationUserCreate.js"; /** * Contract definition for authentication admin component. 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 77c1a06..b458db9 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -134,10 +134,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA const existingIdentity = await this._userEntityStorage.get(userIdentity, "identity"); if (Is.object(existingIdentity)) { - throw new GeneralError( - EntityStorageAuthenticationAdminService.CLASS_NAME, - "identityInUse" - ); + throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "identityInUse"); } const saltBytes = RandomHelper.generate(16); diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts index f3f546d..663df44 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -2,8 +2,11 @@ // SPDX-License-Identifier: Apache-2.0. import type { IHttpRequestContext, IHttpResponse } from "@3sixty/api-models"; import { ComponentFactory } from "@3sixty/core"; -import { EntityStorageConnectorFactory } from "@3sixty/entity-storage-models"; -import { VaultConnectorFactory } from "@3sixty/vault-models"; +import { + EntityStorageConnectorFactory, + type IEntityStorageConnector +} from "@3sixty/entity-storage-models"; +import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import { HeaderTypes } from "@3sixty/web"; import { AuthHeaderProcessor } from "../../src/processors/authHeaderProcessor.js"; import { authenticationAdoptIdentity } from "../../src/routes/entityStorageAuthenticationRoutes.js"; @@ -11,19 +14,22 @@ import { authenticationAdoptIdentity } from "../../src/routes/entityStorageAuthe describe("authenticationAdoptIdentity", () => { beforeEach(() => { vi.restoreAllMocks(); - vi.spyOn(EntityStorageConnectorFactory, "get").mockReturnValue({}); - vi.spyOn(VaultConnectorFactory, "get").mockReturnValue({}); + const entityStorage = {} as unknown as IEntityStorageConnector; + const vault = {} as unknown as IVaultConnector; + vi.spyOn(EntityStorageConnectorFactory, "get").mockReturnValue(entityStorage); + vi.spyOn(VaultConnectorFactory, "get").mockReturnValue(vault); vi.spyOn(ComponentFactory, "getIfExists").mockReturnValue(undefined); }); it("should leave the token out of the body and out of the cookie for a bearer caller", async () => { - vi.spyOn(ComponentFactory, "get").mockReturnValue({ + const authentication = { className: () => "authentication", adoptIdentity: vi.fn().mockResolvedValue({ token: "new-jwt-token", expiry: 1722514341067 }) - } as unknown as ReturnType); + }; + vi.spyOn(ComponentFactory, "get").mockReturnValue(authentication); const httpRequestContext = { serverRequest: { headers: {} }, processorState: { @@ -54,13 +60,14 @@ describe("authenticationAdoptIdentity", () => { }); it("should set the new token as a cookie when the caller did not use a bearer token", async () => { - vi.spyOn(ComponentFactory, "get").mockReturnValue({ + const authentication = { className: () => "authentication", adoptIdentity: vi.fn().mockResolvedValue({ token: "new-jwt-token", expiry: 1722514341067 }) - } as unknown as ReturnType); + }; + vi.spyOn(ComponentFactory, "get").mockReturnValue(authentication); const httpRequestContext = { serverRequest: { headers: {} }, processorState: {} 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 8cf2ceb..1c7c41e 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 @@ -16,6 +16,7 @@ import { import { nameof } from "@3sixty/nameof"; import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import type { AuthenticationUser } from "../../src/entities/authenticationUser.js"; +import type { IIdentityAdoption } from "../../src/models/IIdentityAdoption.js"; import { initSchema } from "../../src/schema.js"; import { EntityStorageAuthenticationService } from "../../src/services/entityStorageAuthenticationService.js"; import { PasswordHelper } from "../../src/utils/passwordHelper.js"; @@ -962,11 +963,7 @@ describe("EntityStorageAuthenticationService", () => { describe("adoptIdentity", () => { const mnemonic = "abandon abandon abandon abandon abandon abandon"; const documentId = "did:iota:tst:abc"; - let identity: { - className: ReturnType; - identityCreateSelf: ReturnType; - identityRemove: ReturnType; - }; + let identity: IIdentityAdoption; let user: AuthenticationUser; beforeEach(async () => { @@ -1010,7 +1007,7 @@ describe("EntityStorageAuthenticationService", () => { it("should replace an email subject with the new DID and issue a token for it", async () => { const result = await service.adoptIdentity(); - expect(identity.identityCreateSelf).toHaveBeenCalledTimes(1); + expect(vi.mocked(identity.identityCreateSelf)).toHaveBeenCalledTimes(1); expect(mockUserEntityStorage.set).toHaveBeenCalledWith( expect.objectContaining({ email: "User@Example.com", @@ -1058,26 +1055,28 @@ describe("EntityStorageAuthenticationService", () => { }); await expect(service.adoptIdentity()).rejects.toBeInstanceOf(ConflictError); - expect(identity.identityCreateSelf).not.toHaveBeenCalled(); + expect(vi.mocked(identity.identityCreateSelf)).not.toHaveBeenCalled(); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); expect(user.identity).toBe("did:iota:tst:existing"); }); it("should leave the stored identity unchanged when identity create throws", async () => { - identity.identityCreateSelf.mockRejectedValue(new Error("publish failed")); + vi.mocked(identity.identityCreateSelf).mockRejectedValue(new Error("publish failed")); await expect(service.adoptIdentity()).rejects.toThrow("publish failed"); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); expect(TokenHelper.createToken).not.toHaveBeenCalled(); expect(user.identity).toBe("user@example.com"); - expect(JSON.stringify(identity.identityCreateSelf.mock.results)).not.toContain("token"); + expect(JSON.stringify(vi.mocked(identity.identityCreateSelf).mock.results)).not.toContain( + "token" + ); }); it("should remove the new DID with itself as controller when the save throws", async () => { vi.mocked(mockUserEntityStorage.set).mockRejectedValue(new Error("save failed")); await expect(service.adoptIdentity()).rejects.toThrow("save failed"); - expect(identity.identityRemove).toHaveBeenCalledWith( + expect(vi.mocked(identity.identityRemove)).toHaveBeenCalledWith( documentId, { removeKeys: true }, documentId @@ -1087,7 +1086,8 @@ describe("EntityStorageAuthenticationService", () => { }); it("should keep a recorded mnemonic out of the failure", async () => { - identity.identityCreateSelf.mockResolvedValue({ id: "not-a-did", mnemonic }); + const created = { id: "not-a-did", mnemonic }; + vi.mocked(identity.identityCreateSelf).mockResolvedValue(created); const rejection = await service.adoptIdentity().then( () => "", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6037d1f..e504065 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,7 +168,7 @@ importers: devDependencies: '@commitlint/cli': specifier: 21.2.3 - version: 21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.2)(typescript@6.0.3) + version: 21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.3)(typescript@6.0.3) '@commitlint/config-conventional': specifier: 21.2.3 version: 21.2.3 @@ -204,7 +204,7 @@ importers: version: 10.1.8(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)) eslint-plugin-import-x: specifier: 4.17.1 - version: 4.17.1(@typescript-eslint/utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2) + version: 4.17.1(@typescript-eslint/utils@8.71.2(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2) eslint-plugin-jsdoc: specifier: 64.5.4 version: 64.5.4(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) @@ -241,25 +241,25 @@ importers: packages/api-auth-entity-storage-models: dependencies: - '@twin.org/core': + '@3sixty/core': specifier: next - version: 0.10.1-next.10 - '@twin.org/nameof': + version: 0.20.0-next.1 + '@3sixty/nameof': specifier: next - version: 0.10.1-next.10 - '@twin.org/web': + version: 0.20.0-next.1 + '@3sixty/web': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 devDependencies: - '@twin.org/nameof-transformer': + '@3sixty/nameof-transformer': specifier: next - version: 0.10.1-next.10 - '@twin.org/nameof-vitest-plugin': + version: 0.20.0-next.1 + '@3sixty/nameof-vitest-plugin': specifier: next - version: 0.10.1-next.10(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) - '@twin.org/validate-locales': + version: 0.20.0-next.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) + '@3sixty/validate-locales': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 '@types/node': specifier: 26.6.2 version: 26.6.2 @@ -292,38 +292,38 @@ importers: version: 6.0.3 vitest: specifier: 5.0.1 - version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) + version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) packages/api-auth-entity-storage-rest-client: dependencies: - '@twin.org/api-auth-entity-storage-models': - specifier: 0.9.2-next.7 + '@3sixty/api-auth-entity-storage-models': + specifier: 0.20.0-next.0 version: link:../api-auth-entity-storage-models - '@twin.org/api-core': + '@3sixty/api-core': specifier: next - version: 0.10.1-next.5(supports-color@10.2.2) - '@twin.org/api-models': + version: 0.20.0-next.0(supports-color@10.2.2) + '@3sixty/api-models': specifier: next - version: 0.10.1-next.5 - '@twin.org/core': + version: 0.20.0-next.0 + '@3sixty/core': specifier: next - version: 0.10.1-next.10 - '@twin.org/nameof': + version: 0.20.0-next.1 + '@3sixty/nameof': specifier: next - version: 0.10.1-next.10 - '@twin.org/web': + version: 0.20.0-next.1 + '@3sixty/web': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 devDependencies: - '@twin.org/nameof-transformer': + '@3sixty/nameof-transformer': specifier: next - version: 0.10.1-next.10 - '@twin.org/nameof-vitest-plugin': + version: 0.20.0-next.1 + '@3sixty/nameof-vitest-plugin': specifier: next - version: 0.10.1-next.10(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) - '@twin.org/validate-locales': + version: 0.20.0-next.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) + '@3sixty/validate-locales': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 '@types/node': specifier: 26.6.2 version: 26.6.2 @@ -356,65 +356,65 @@ importers: version: 6.0.3 vitest: specifier: 5.0.1 - version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) + version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) packages/api-auth-entity-storage-service: dependencies: - '@twin.org/api-auth-entity-storage-models': - specifier: 0.9.2-next.7 + '@3sixty/api-auth-entity-storage-models': + specifier: 0.20.0-next.0 version: link:../api-auth-entity-storage-models - '@twin.org/api-core': + '@3sixty/api-core': specifier: next - version: 0.10.1-next.5(supports-color@10.2.2) - '@twin.org/api-models': + version: 0.20.0-next.0(supports-color@10.2.2) + '@3sixty/api-models': specifier: next - version: 0.10.1-next.5 - '@twin.org/background-task-models': + version: 0.20.0-next.0 + '@3sixty/background-task-models': specifier: next - version: 0.10.1-next.1 - '@twin.org/context': + version: 0.20.0-next.0 + '@3sixty/context': specifier: next - version: 0.10.1-next.10 - '@twin.org/core': + version: 0.20.0-next.1 + '@3sixty/core': specifier: next - version: 0.10.1-next.10 - '@twin.org/crypto': + version: 0.20.0-next.1 + '@3sixty/crypto': specifier: next - version: 0.10.1-next.10 - '@twin.org/entity': + version: 0.20.0-next.1 + '@3sixty/entity': specifier: next - version: 0.10.1-next.10 - '@twin.org/entity-storage-models': + version: 0.20.0-next.1 + '@3sixty/entity-storage-models': specifier: next - version: 0.10.1-next.9 - '@twin.org/logging-models': + version: 0.20.0-next.0 + '@3sixty/logging-models': specifier: next - version: 0.10.1-next.2 - '@twin.org/nameof': + version: 0.20.0-next.0 + '@3sixty/nameof': specifier: next - version: 0.10.1-next.10 - '@twin.org/vault-models': + version: 0.20.0-next.1 + '@3sixty/vault-models': specifier: next - version: 0.10.1-next.3 - '@twin.org/web': + version: 0.20.0-next.0 + '@3sixty/web': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 devDependencies: - '@twin.org/entity-storage-connector-memory': + '@3sixty/entity-storage-connector-memory': specifier: next - version: 0.10.1-next.9 - '@twin.org/nameof-transformer': + version: 0.20.0-next.0 + '@3sixty/nameof-transformer': specifier: next - version: 0.10.1-next.10 - '@twin.org/nameof-vitest-plugin': + version: 0.20.0-next.1 + '@3sixty/nameof-vitest-plugin': specifier: next - version: 0.10.1-next.10(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) - '@twin.org/ts-to-openapi': + version: 0.20.0-next.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) + '@3sixty/ts-to-openapi': specifier: next - version: 0.9.3-next.1 - '@twin.org/validate-locales': + version: 0.20.0-next.1 + '@3sixty/validate-locales': specifier: next - version: 0.10.1-next.10 + version: 0.20.0-next.1 '@types/node': specifier: 26.6.2 version: 26.6.2 @@ -447,10 +447,92 @@ importers: version: 6.0.3 vitest: specifier: 5.0.1 - version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) + version: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) packages: + '@3sixty/api-core@0.20.0-next.0': + resolution: {integrity: sha512-tPyKJpr7Odcaa99U1dfCvy0UUiV7sZH7rBsFBJ5obXdlSXd0/aC6Sh2iFJpMShkr0NLTB7v4PhKE75BDvz/Qyw==} + engines: {node: '>=24.0.0'} + + '@3sixty/api-models@0.20.0-next.0': + resolution: {integrity: sha512-AShxxmk3JR5dvLyC3zXYsBb4FZbtoNYsRlB2TUynLk4epOSZcZN+p8Ya+cMM+vsbLT8joA/Jk8TC9fPSEKSk3g==} + engines: {node: '>=24.0.0'} + + '@3sixty/background-task-models@0.20.0-next.0': + resolution: {integrity: sha512-CDLBSjZaTb81FFCV5OscA5G4gEAQb5zaKK4H9BcQiJnTf1k8U1BGeU+O5U+H9PM1v3Trm3h7eEqFhMbXKLonIQ==} + engines: {node: '>=24.0.0'} + + '@3sixty/cli-core@0.20.0-next.1': + resolution: {integrity: sha512-VB4s0nLHJEtZjq+bSR2OQ29FUWc4wspuhnqQPwRjRWPcd55fh//CRrtt/iy+e3TOEN/q77+9ZA+ur5Evowc+UA==} + engines: {node: '>=24.0.0'} + + '@3sixty/context@0.20.0-next.1': + resolution: {integrity: sha512-5oQ9CPbf61ixU8zJVnKIqaGx7aKYN7Bprg+Eoo0dFgOej3Yu4QlTuh6tpg6i49vjqmQzJ16fpLbiSXAGgq9btw==} + engines: {node: '>=24.0.0'} + + '@3sixty/core@0.20.0-next.1': + resolution: {integrity: sha512-svxI2o8AOBf0N58kNim2DybTZSMDFhrl+dv334QikJsBNI9Xj8C6Rbja83ROlpNChTul5mCq+Q/uWzR/+3tqLA==} + engines: {node: '>=24.0.0'} + + '@3sixty/crypto@0.20.0-next.1': + resolution: {integrity: sha512-Na+2Ze7DtepPfO7RfOm0ztTJsPDBrNx5n+BJfiespJ2+kggDet+FcL+lPilrNNHCQO3k6jzaOa/l01s6p0OVIw==} + engines: {node: '>=24.0.0'} + + '@3sixty/entity-storage-connector-memory@0.20.0-next.0': + resolution: {integrity: sha512-jN76Ljx3Xmqx6sng1aBR6WE66mcxUxEwWON5CzBjBRq21PvJdS7jFmXz6JPJH387dRzy5ar76W9yenrES5ajrQ==} + engines: {node: '>=24.0.0'} + + '@3sixty/entity-storage-models@0.20.0-next.0': + resolution: {integrity: sha512-IG5JgBzpNScXF2+XExjFYLnSSbsFfYGCERXMx6JpB1NRzD7XvwmUWUkvBxGjcXhayEqnE0eEiEdXRp7Xevt3Iw==} + engines: {node: '>=24.0.0'} + + '@3sixty/entity@0.20.0-next.1': + resolution: {integrity: sha512-nQyZZE5Epkz8V5BNldZvlG1joWCcUgBoQpbezqTg3XJCRbZTfafeuQJ+2Yu4iQ/Wy2URaA0/AMiH19tzupgXPQ==} + engines: {node: '>=24.0.0'} + + '@3sixty/logging-models@0.20.0-next.0': + resolution: {integrity: sha512-I4EunSdIs/pHPAM0uGw9T8KDWluGFtWJ5n+jYCW9Vwgk/OF4Vszila/IdSt0NN2kVYzGj4sCD6CvPtJOcT+FXw==} + engines: {node: '>=24.0.0'} + + '@3sixty/nameof-transformer@0.20.0-next.1': + resolution: {integrity: sha512-2Fk/ZfbHMn/KTPibs7WFZ2K5azOTs9QqCLjMXKx5faznRLTuQGVD3+q5uuDgjOwW9XL8I7hZY08Dtp0gO40mEA==} + engines: {node: '>=24.0.0'} + + '@3sixty/nameof-vitest-plugin@0.20.0-next.1': + resolution: {integrity: sha512-MSAZfOQFteeh/84sgNkesuoF7Z6WrlZqUlm6cVoPOhg9660r60TNNO1gBSbyV1K/9Njlk0YI0WNX+BidNbHpSg==} + engines: {node: '>=24.0.0'} + + '@3sixty/nameof@0.20.0-next.1': + resolution: {integrity: sha512-lj8xKghhaBsfPtkUcZ8dk41xQwTv/KgEE69cibAJxC6KzsuhM0Dbp8C0L6R8bRDkg2vnO7flxaHWBEBl9EQK1Q==} + engines: {node: '>=24.0.0'} + + '@3sixty/tools-core@0.20.0-next.1': + resolution: {integrity: sha512-axh0z9bTmoMh/hAMQnDMIN+dKE+TlQ3z7w8D7jvdwBd/ZqPefQwHMl9WFSVQjGN70LHuq7Lj1hwPww4YK12y5w==} + engines: {node: '>=24.0.0'} + + '@3sixty/tools-models@0.20.0-next.1': + resolution: {integrity: sha512-qE5lkX1D7oQHvZH56F1KN8T8rIxPhd9WxuFoUpGHV4U4ibccbOXcJGcQNAcW/VtvDGVjbgxqo0Q6wDn3sGT8cw==} + engines: {node: '>=24.0.0'} + + '@3sixty/ts-to-openapi@0.20.0-next.1': + resolution: {integrity: sha512-LORoVzeNk7LcrgOOAnex94STxaEGZi4f7W+cxEWlCpDDLukyciTV0EiNgloITcmBiI0ey3No1zY3QVe7eZVOlA==} + engines: {node: '>=24.0.0'} + hasBin: true + + '@3sixty/validate-locales@0.20.0-next.1': + resolution: {integrity: sha512-FAZbKQGllTqhYNaqT+ctHcKBJeZBinrjO5z3W4lItmT3f3FkO1BHxk/q5/kN8D00tEc+XMd/yvNunEf4ix50Bw==} + engines: {node: '>=24.0.0'} + hasBin: true + + '@3sixty/vault-models@0.20.0-next.0': + resolution: {integrity: sha512-6XQPJ+IKkiue0xTLCD2VtZP+HjokVEklkZuMYiOTFmFDGX1H5Ai+Qx3F4jO9jXvfP5bNjce4b5byhuPKC49N0A==} + engines: {node: '>=24.0.0'} + + '@3sixty/web@0.20.0-next.1': + resolution: {integrity: sha512-lj2MNibw+Lj8QfDQX5MA/b3K9+2YKUAUitDvO3PIZqo5zRH38YtEYhUL2ioa355ZCjuZ9RdTjWO4n7stt0NAZg==} + engines: {node: '>=24.0.0'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -551,12 +633,12 @@ packages: resolution: {integrity: sha512-sGEA473TlCvhCGNoTBY9OXZVWdscIXdGt0dm8EpB+2MRfmt8LNh/X56P/T3nCtEB+eH5qGEa3odphSAoUxSfSQ==} engines: {node: '>=22.12.0'} - '@conventional-changelog/git-client@3.1.2': - resolution: {integrity: sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==} + '@conventional-changelog/git-client@3.2.0': + resolution: {integrity: sha512-sD/E0vzpkoDz/dqM1Hhv0ihC4KBw6RXjefeO8SG1XrX+wGkMxe1pKeedkKDPCtr2JjuhbuS9aJNgOvnT8hbeBw==} engines: {node: '>=22'} peerDependencies: conventional-commits-filter: ^6.0.1 - conventional-commits-parser: ^7.1.2 + conventional-commits-parser: ^7.1.3 peerDependenciesMeta: conventional-commits-filter: optional: true @@ -617,8 +699,8 @@ packages: '@cspell/dict-companies@3.2.13': resolution: {integrity: sha512-mBJeYzCkiiWgNQxKg7zyQOpFPwcXO4IAzXGbOEFfcQ4Q0G5Fa7Kqd8z+gvnvSK7NPWKJaFmiApC6I0cka02qmQ==} - '@cspell/dict-cpp@7.1.2': - resolution: {integrity: sha512-bkR33oE+ToyD1hwSV4XeEUB9oDkizHRX03CrQfj23tX4VUVbie5vkEHzlKVhcw/Q+fO2dtV/Vz4f6O5M4a2iZQ==} + '@cspell/dict-cpp@7.1.3': + resolution: {integrity: sha512-2d2MSh+C+xzzdqjLwtt40TdXWDR/pK0q1Q9L5zC+1EPSCIZxlN7CHbhlXWpl0JqUSdy2xaWaWucwnoGznMTneA==} '@cspell/dict-cryptocurrencies@5.0.5': resolution: {integrity: sha512-R68hYYF/rtlE6T/dsObStzN5QZw+0aQBinAXuWCVqwdS7YZo0X33vGMfChkHaiCo3Z2+bkegqHlqxZF4TD3rUA==} @@ -635,8 +717,8 @@ packages: '@cspell/dict-data-science@2.0.16': resolution: {integrity: sha512-M72mxv5asuAnORurz4iXRJ+Tw9XBq6eu7D2Ne7biP0Z1RciKGNxXWu9JycA/KlVvK1hAlKj/fANlXhuEWpXKFg==} - '@cspell/dict-django@4.1.6': - resolution: {integrity: sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==} + '@cspell/dict-django@4.1.7': + resolution: {integrity: sha512-94LIxiF/StBTxDkmBhi1eoOX9D/B7gk6pabBvIZFh0gmREAU6ESqU2Wv/RfGARdueMQMPfOJka+vbtL4yKv0oA==} '@cspell/dict-docker@1.1.17': resolution: {integrity: sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==} @@ -647,14 +729,14 @@ packages: '@cspell/dict-elixir@4.0.8': resolution: {integrity: sha512-CyfphrbMyl4Ms55Vzuj+mNmd693HjBFr9hvU+B2YbFEZprE5AG+EXLYTMRWrXbpds4AuZcvN3deM2XVB80BN/Q==} - '@cspell/dict-en-common-misspellings@2.2.2': - resolution: {integrity: sha512-gu0zS7nm0jgVA2EswgWOR8od8wbNYVgcGzo0pGaDqcSkKS4WhABehLOwbyI0NIEVB+ME6SmckLVGk7g+VD1C6A==} + '@cspell/dict-en-common-misspellings@2.2.5': + resolution: {integrity: sha512-c1bj+HXT8fafxrBT/nmVLwr7ILVomgVSGlWsb2wjJYanqvLJHXkCVmr0sM97F9FJeWQFIgwrRi5MWSYbNQAQ6A==} - '@cspell/dict-en-gb-mit@3.1.29': - resolution: {integrity: sha512-mInExsCFZui0f9Vg/MToWfQZzwMfPgHX8BCYdZNdXWbYz+jITXcnvssbdUkui/DVbriZVJ74Zu9bv2jOEqTCNA==} + '@cspell/dict-en-gb-mit@3.1.30': + resolution: {integrity: sha512-wmsPqPrO+GRfD8iA2jSKzRdEVd46ZbnDttRWM8RC4a8DkeiIanTtIgqX5V23jJibSgpsRVAtY+ktTw2nOK/Yvw==} - '@cspell/dict-en_us@4.4.40': - resolution: {integrity: sha512-TGyOaWP1dD28GRHH2Lt04ciMZC8Z6UX+5kJsi04pbQHq6uRxv14/bBVCBeSsajUXRZsJYUqXyHaHB9O0ODuFuQ==} + '@cspell/dict-en_us@4.4.41': + resolution: {integrity: sha512-PsfGqN/dSSPpaSl1vKaPI4DHAmgbFITON398YUEvoduDM1mWXnDOL+qBQ/ZjpWs5+aaShPq5c1vpJg8fuQFOQw==} '@cspell/dict-filetypes@3.0.18': resolution: {integrity: sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==} @@ -671,8 +753,8 @@ packages: '@cspell/dict-fullstack@3.2.9': resolution: {integrity: sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==} - '@cspell/dict-gaming-terms@1.1.2': - resolution: {integrity: sha512-9XnOvaoTBscq0xuD6KTEIkk9hhdfBkkvJAIsvw3JMcnp1214OCGW8+kako5RqQ2vTZR3Tnf3pc57o7VgkM0q1Q==} + '@cspell/dict-gaming-terms@1.1.3': + resolution: {integrity: sha512-hLGzd/f+KX5B/5YxAXuQi4DYEfu6IKudtNFO/Nima3JLXD+8Xn+d0q6VdaBxkWVhGYlzow9o+dce+S9mNtgpMg==} '@cspell/dict-git@3.1.1': resolution: {integrity: sha512-XvuGVYj9YEu4EzntqyhH8XlTRyy57C6j8oxkIbUQfR2UYzxZGAP77XHl7qY7eC/40r/haDwnBaccyFR6BRmUCg==} @@ -730,8 +812,8 @@ packages: '@cspell/dict-node@5.0.9': resolution: {integrity: sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==} - '@cspell/dict-npm@5.2.50': - resolution: {integrity: sha512-JZhJb0wrWpqnzDBM1VG8f6zZPRCOo6YMxct0hFhgBnbOTIHQiqZf9S1BSXCRMmmQlAWqFXjKNDp+rf7ZiJcO9w==} + '@cspell/dict-npm@5.2.54': + resolution: {integrity: sha512-7jQPRZL1XnZ0HPk5iYAq5WpJ5dpgKHRDBl9aB6uQQBJdo90mCF4bjPGSVl2HrWgx+IXlafiyx/QNO2XII/hgYg==} '@cspell/dict-php@4.1.3': resolution: {integrity: sha512-41oKzl7rLVrrVZA/Gs0ESnP6KliEX7c+IwaaNmOL3PAoHasFDVEmIFeENIzuDdM12UcHT+MlLkAm3Qw/lS/ERA==} @@ -739,8 +821,8 @@ packages: '@cspell/dict-powershell@5.0.17': resolution: {integrity: sha512-khOoIdBgKRk2scnr7888pf70yJ1Tr3RGdnYcAN0MJ2vtaNRKoOTCFc13gXUkNDHwEyhMI1h9oz3KsT180F2amw==} - '@cspell/dict-public-licenses@2.0.16': - resolution: {integrity: sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==} + '@cspell/dict-public-licenses@2.0.17': + resolution: {integrity: sha512-Asdumgg3oZ4ki//b2a+beiVIsNZA5thim4t4v3lTDLY/6a8VAPmTjzC3Y2t0x7ywC/bzAhi1KW/fg8JlwHDgig==} '@cspell/dict-python@4.5.2': resolution: {integrity: sha512-CFcTDGN/ycn45WXC5ZZ06pG09WbGKMpPm1rkMuqMP98/jwjw3iiK8c0izWRd8x027n1rOZ9kJ8f9Sd+bQ22HJg==} @@ -760,8 +842,8 @@ packages: '@cspell/dict-shell@1.2.0': resolution: {integrity: sha512-PVctvT22lJ49niMiakO8xieY7ELCAzjSqhejWR7bAMb5AZ9F4WDEs+XdGMnoVHWeXq7K5rcepLPmEJb+37zzIw==} - '@cspell/dict-software-terms@5.4.5': - resolution: {integrity: sha512-5PgLJNdaFKeA6iklB0XXjmgNaoLJJqf54oRTD1raG/mgwojVlRnWE1G7x4QH9Gz07h3B3+lMDv1F57Xxs4jang==} + '@cspell/dict-software-terms@5.4.8': + resolution: {integrity: sha512-7Fv/jgRghpcEcvpmrRR54gPp1l85pPLmlMua74hjhMJoWklAp905V742XR6u5HwvGRH7aBTW9euNsJULav1E6A==} '@cspell/dict-sql@2.2.1': resolution: {integrity: sha512-qDHF8MpAYCf4pWU8NKbnVGzkoxMNrFqBHyG/dgrlic5EQiKANCLELYtGlX5auIMDLmTf1inA0eNtv74tyRJ/vg==} @@ -843,8 +925,8 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.1.1': - resolution: {integrity: sha512-A+s89eP+yDAPkKjnIOfjlkGifg5J7GPn01nWx14UjBVWktfkxOU4975F9ZZ13xNxkGiblNcBaAqgKhnQrW6SWA==} + '@eslint/css-tree@4.1.2': + resolution: {integrity: sha512-rVcxLmwSkEL3aWS3vAgsTRrQHoYE1b0XcgDtKHDJ92XOkyUpcbS/g0tUJKhvCtlEhYzgKBWsgDd+Z/aPhd166w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/eslintrc@3.3.7': @@ -917,14 +999,14 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@jsep-plugin/assignment@1.3.0': - resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} + '@jsep-plugin/assignment@1.4.0': + resolution: {integrity: sha512-nw2Px23Eirx0PFTVoPrb3FEq9Wkmg6vxcDtt38MtBCf1G0tjLCB9cK+3seOXyiw+6+aalgYhvJabQbWwMVKlEQ==} engines: {node: '>= 10.16.0'} peerDependencies: jsep: ^0.4.0||^1.0.0 - '@jsep-plugin/regex@1.0.4': - resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} + '@jsep-plugin/regex@1.1.0': + resolution: {integrity: sha512-2Vkbda+XRjK6VTI6Lrh0sNBuMT8mZ51EhqMvO+V2rSdmqjs7NAGssxPZfkL23EFzYfSrvxyd1ZjOdqPQMM50og==} engines: {node: '>= 10.16.0'} peerDependencies: jsep: ^0.4.0||^1.0.0 @@ -938,8 +1020,8 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} - '@napi-rs/wasm-runtime@1.2.4': - resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + '@napi-rs/wasm-runtime@1.2.5': + resolution: {integrity: sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 @@ -1021,8 +1103,8 @@ packages: '@octokit/types@13.10.0': resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} - '@oxc-project/types@0.150.0': - resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + '@oxc-project/types@0.154.0': + resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} @@ -1032,98 +1114,98 @@ packages: resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} engines: {node: '>=18'} - '@rolldown/binding-android-arm-eabi@1.2.9': - resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} + '@rolldown/binding-android-arm-eabi@1.2.14': + resolution: {integrity: sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.9': - resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==} + '@rolldown/binding-android-arm64@1.2.14': + resolution: {integrity: sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.9': - resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==} + '@rolldown/binding-darwin-arm64@1.2.14': + resolution: {integrity: sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.9': - resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==} + '@rolldown/binding-darwin-x64@1.2.14': + resolution: {integrity: sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.9': - resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==} + '@rolldown/binding-freebsd-x64@1.2.14': + resolution: {integrity: sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': - resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': + resolution: {integrity: sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.9': - resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==} + '@rolldown/binding-linux-arm64-gnu@1.2.14': + resolution: {integrity: sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.9': - resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==} + '@rolldown/binding-linux-arm64-musl@1.2.14': + resolution: {integrity: sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.9': - resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.14': + resolution: {integrity: sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.9': - resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.14': + resolution: {integrity: sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.9': - resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==} + '@rolldown/binding-linux-x64-gnu@1.2.14': + resolution: {integrity: sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.9': - resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==} + '@rolldown/binding-linux-x64-musl@1.2.14': + resolution: {integrity: sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.9': - resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==} + '@rolldown/binding-openharmony-arm64@1.2.14': + resolution: {integrity: sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.9': - resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==} + '@rolldown/binding-win32-arm64-msvc@1.2.14': + resolution: {integrity: sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.9': - resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==} + '@rolldown/binding-win32-x64-msvc@1.2.14': + resolution: {integrity: sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1185,88 +1267,6 @@ packages: peerDependencies: eslint: '>=7.7.0' - '@twin.org/api-core@0.10.1-next.5': - resolution: {integrity: sha512-Yelkl8WiqJSxtSHatzCcpnJRG73R+S+mKfXhr74+zjgltINxgvzLH5UBp0o7cBkGubfFelOR6lAacfPhrHN0TA==} - engines: {node: '>=24.0.0'} - - '@twin.org/api-models@0.10.1-next.5': - resolution: {integrity: sha512-JpUka41YqxNjhxPjdwkt6wweAAaB29+oWkVWzSfiREMoS/428rrAnpqDPkyIkM2VQzlSIrkBeu3IZiiplo5vlQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/background-task-models@0.10.1-next.1': - resolution: {integrity: sha512-uopPfGHVwOkKbzB+aa3WboJOKERrsESFg8NLg/El7QcS4UANNbMCiDUpJTeejO5JPhvjEBmRclcYzfVS1bQ+jQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/cli-core@0.10.1-next.10': - resolution: {integrity: sha512-G+i9ge3XzLyscOd32lKxoFiz10BRsL+jJG8rfFLTnGqRo0p50gxac7CCNmJlZdwpf5XYYMtYsqV5tBwX8fTQRw==} - engines: {node: '>=24.0.0'} - - '@twin.org/context@0.10.1-next.10': - resolution: {integrity: sha512-RZXRmxXvbnpidhLVJx5KPy6UpI7QDcA/ZYbHA3JXNCGS14gIcMzl33ArKtVmYP27OETmddInLLGTVr/V6LHHFw==} - engines: {node: '>=24.0.0'} - - '@twin.org/core@0.10.1-next.10': - resolution: {integrity: sha512-VJe9xzydp1OtJ94aW2KFIMxzhxDp1fDopH1p6E7s/HZQjVsC8qJ32X1f7FlE0KFcxA3DfSdg7Ektg82La0J44g==} - engines: {node: '>=24.0.0'} - - '@twin.org/crypto@0.10.1-next.10': - resolution: {integrity: sha512-sCl+2Syse8J2kUF3ly4crLN3kIXgc6eSA4zoeWl4csjKpOwG9/qAhHcapgzkp1YT9KXxlnBVI3en7CDNEj2PTg==} - engines: {node: '>=24.0.0'} - - '@twin.org/entity-storage-connector-memory@0.10.1-next.9': - resolution: {integrity: sha512-6HnXc1FigwL7wjn68KV/1C9YacXD+7UzLiDhzIspRQdwAI/RVjAgzLilBvbvHmIVg9s2gP6pQMiRPT2a/jc3FA==} - engines: {node: '>=24.0.0'} - - '@twin.org/entity-storage-models@0.10.1-next.9': - resolution: {integrity: sha512-RDHwYjU7dNX9HK+vZDNVtmxFO+AOutoJhklm/moqjral3+lzXHNS9OQUZgpC3iLpGpxBqEYgFUR8RRgfFGo+AA==} - engines: {node: '>=24.0.0'} - - '@twin.org/entity@0.10.1-next.10': - resolution: {integrity: sha512-7EWfC+XyXgyNEf1DVB8IAmgqfIArWUhsLVwAbfsCkCo3TNtJlse32fQND49QHt4FNIC5QOpuO4v9N4Q61GjiLQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/logging-models@0.10.1-next.2': - resolution: {integrity: sha512-6R3oOKw0qS91S2lxcshKxD1XAkj8lv4fKVOmo/QvWMjaekW3TEzmgJ4ekUM2tVigaUUoxx/IuVcfa9ZSRyLPKA==} - engines: {node: '>=24.0.0'} - - '@twin.org/nameof-transformer@0.10.1-next.10': - resolution: {integrity: sha512-ZgGHsOXjodmFHn1DqpBskBpMsz1MndfZ9rytxz2vK4uGhTq4+OItUEr04THtBfRd5FTUpTOZ66nVMupmlPObHQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/nameof-vitest-plugin@0.10.1-next.10': - resolution: {integrity: sha512-bjXGQqpwz5jzwOKr1U6OvInIPUKZ+qIFp96hJZS+odu80LkHlxMEeYjrK34imzISeaP5qHInozfWuMt5B0cfdA==} - engines: {node: '>=24.0.0'} - - '@twin.org/nameof@0.10.1-next.10': - resolution: {integrity: sha512-A5y6oUF2DBdqtsyw+UnHF8ngcHpnx2CLNqzKykGVKfxv5OeE4bVWWIXQPDgcC8+N5b/1pd4XNwNWA4Jpr2RK+g==} - engines: {node: '>=24.0.0'} - - '@twin.org/tools-core@0.9.3-next.1': - resolution: {integrity: sha512-RLbgAYB65n8TByCb8YdEbiw/+bl4KKfGGFs+hU6aFfsj/SNGljBl+yl1e400NJ2MZ1gOu8cFo5bbXSKGsglkyQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/tools-models@0.9.3-next.1': - resolution: {integrity: sha512-B1UgoaaQIm9HaiQW8NZY2afDAqj07p195iZ9uSWVh4SFnSQMnNyBEp7iYMmNGJoGuQ/Bz0Uia59hNFn3/0Lrew==} - engines: {node: '>=24.0.0'} - - '@twin.org/ts-to-openapi@0.9.3-next.1': - resolution: {integrity: sha512-qH0bvYPktD/9o964LpQqHS3GwisRIy7e6DcnTWcdsPb/MiGN5jPanfESGW4wPEfiu3u4J8XTMIOX7noO3QioWQ==} - engines: {node: '>=24.0.0'} - hasBin: true - - '@twin.org/validate-locales@0.10.1-next.10': - resolution: {integrity: sha512-MXcVGNzhuUVaCcvFCvr6tCnlCV/ftYCUr7LxNPmgCBtQur3HDNX6TKsZWX2ONuo+4DnBlDYf2kMex77Z0pmFjA==} - engines: {node: '>=24.0.0'} - hasBin: true - - '@twin.org/vault-models@0.10.1-next.3': - resolution: {integrity: sha512-bVycokwJ1QN69e+ILVul5z5Qt2INK/+guIXEshbAN7bHOsFQZsjGDR83/0VYLbM5rX7NCeVGlD7WfIUfYm1WTQ==} - engines: {node: '>=24.0.0'} - - '@twin.org/web@0.10.1-next.10': - resolution: {integrity: sha512-UVbKlPveXGtHsu+xzva1AoHAl+UiF5xwzuhV234P1P9uEC3dxAaAmb3X4q+thYM0ey2octhcJ9Y0ndXcMpn4Hw==} - engines: {node: '>=24.0.0'} - '@tybys/wasm-util@0.10.4': resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} @@ -1336,16 +1336,32 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.71.2': + resolution: {integrity: sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.70.1': resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.71.2': + resolution: {integrity: sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.70.1': resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/tsconfig-utils@8.71.2': + resolution: {integrity: sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/type-utils@8.70.1': resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1357,12 +1373,22 @@ packages: resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.71.2': + resolution: {integrity: sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.70.1': resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/typescript-estree@8.71.2': + resolution: {integrity: sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.70.1': resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1370,10 +1396,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/utils@8.71.2': + resolution: {integrity: sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/visitor-keys@8.70.1': resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.71.2': + resolution: {integrity: sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} cpu: [arm] @@ -1503,12 +1540,12 @@ packages: '@vitest/browser': optional: true - '@vitest/istanbul-lib-coverage@1.0.1': - resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + '@vitest/istanbul-lib-coverage@1.0.2': + resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} engines: {node: '>=22'} - '@vitest/istanbul-lib-report@1.0.1': - resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + '@vitest/istanbul-lib-report@1.0.2': + resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} engines: {node: '>=22'} '@vitest/mocker@5.0.1': @@ -1534,8 +1571,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} engines: {node: '>=0.4.0'} hasBin: true @@ -1553,8 +1590,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.3.0: - resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -1576,8 +1613,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - argparse@3.0.2: - resolution: {integrity: sha512-mFdDM6WqWKraGLsVb+C9CahPnzTXOefAOLq3jYcca2YZ8bEWpr++Tzj+zSaKW9+X9L5uSxcm1AZ3Y6aZJ09OhQ==} + argparse@3.0.3: + resolution: {integrity: sha512-Sn98ftV3KfsO8xfLPHG6oT8y+b0yvbrLgZjgPDXsharNY15tTAjqIsIju4S32nl6/iEUNNE4v+yL2Zhm8ooP3w==} argue-cli@3.2.0: resolution: {integrity: sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==} @@ -1610,8 +1647,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.25: - resolution: {integrity: sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==} + baseline-browser-mapping@2.11.29: + resolution: {integrity: sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==} engines: {node: '>=6.0.0'} hasBin: true @@ -1636,8 +1673,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.29.0: - resolution: {integrity: sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==} + browserslist@4.29.3: + resolution: {integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1660,15 +1697,15 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} - caniuse-lite@1.0.30001810: - resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001816: + resolution: {integrity: sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} - chalk-template@1.1.2: - resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} + chalk-template@1.1.3: + resolution: {integrity: sha512-KDt7oQ+LRF2VLFsTAZd76ZNCKQa8LuW7SZkNJMkriMzvlSNj6dI+S+f41mfK9oTtil49seHVyabtt1753sFL2w==} engines: {node: '>=14.16'} chalk@4.1.2: @@ -1683,6 +1720,10 @@ packages: resolution: {integrity: sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==} engines: {node: '>=22'} + chalk@6.0.1: + resolution: {integrity: sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==} + engines: {node: '>=22'} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -1752,8 +1793,8 @@ packages: resolution: {integrity: sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==} engines: {node: '>=22'} - conventional-changelog-conventionalcommits@10.4.0: - resolution: {integrity: sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==} + conventional-changelog-conventionalcommits@10.4.1: + resolution: {integrity: sha512-jS1LRFSbRoAr6JbhhRy1JYfNNUj5LM3+v49hRE03CgccsiYqgdVj/RTmrw4J1rylrqijHwdOlJWiJa61JHOgHg==} engines: {node: '>=22'} conventional-changelog-conventionalcommits@6.1.0: @@ -1769,8 +1810,8 @@ packages: resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} engines: {node: '>=14'} - conventional-commits-parser@7.1.2: - resolution: {integrity: sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==} + conventional-commits-parser@7.1.3: + resolution: {integrity: sha512-Q9oWm1o9uy/oEsZQIraoFMZ4RuPD+79KgwWj6mJVP356tazolCB0piRLZ5Kz9te9p027uSEcUW+Gu44Eh+X79g==} engines: {node: '>=22'} hasBin: true @@ -1923,13 +1964,13 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - dotenv@18.0.1: - resolution: {integrity: sha512-0eR4m4D/jH5eaI3evo2ZqqMii5mrTCR12v12VChdie6O1gPHs7XAjzTep0CWc1Bd+oNx3vVfbi734dK4zghigw==} + dotenv@18.0.2: + resolution: {integrity: sha512-wl0U3CxC0iobaryqEKpFJxzyp4AxQFYAKj/WWszXZr87vVHLi2yJv2syax5Vxt+u02xqNgE277rsc3lOKg9sPg==} engines: {node: '>=12'} hasBin: true - electron-to-chromium@1.5.433: - resolution: {integrity: sha512-5lCAbyZBjtmUt/RAGHRqrL2q0oEFRThDAsZHHDn9XHa89Qw7gMYOeSicBTy+AHfvo0r6vwsZvqNJTQIQy1BLzA==} + electron-to-chromium@1.5.459: + resolution: {integrity: sha512-4SC9HHSFjO/liAhifpdOL5hG9b4BP7/5G3y3UPZVO8trlAXr1CaUEEJL0gWK3kOcYcZrOA5rkwG45Gra/lTIgg==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -1937,8 +1978,8 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - engine.io-client@6.6.6: - resolution: {integrity: sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==} + engine.io-client@6.6.7: + resolution: {integrity: sha512-GyAvmPA7gnRCpDQbYACAy79Lg4ewevrmaPzySZz0OVvgyZc3gEfLLqPTYk8wVBScjOEpM9ZO4KryyFpVItjGEA==} engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} @@ -2135,8 +2176,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-equals@6.0.4: - resolution: {integrity: sha512-JiBeR0k76FIahi9zAtQR7X1jgZDKLE+fs5S9ryjOGYZWZCOcm8QiwgeahkFvaboS3i6PkGBopRznHKuKhm9zpg==} + fast-equals@6.2.1: + resolution: {integrity: sha512-s7KFkLPAAbIDGTh+nVYhjTiwKGvgMXAUG5vwzg8vQKj7NSRZ4eEGExBQ4rq9V90fTYVWd1cb/Wr4ffCNBmLQMQ==} engines: {node: '>=6.0.0'} fast-glob@3.3.3: @@ -2255,8 +2296,8 @@ packages: resolution: {integrity: sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==} engines: {node: '>=20'} - handlebars@4.7.9: - resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + handlebars@4.7.10: + resolution: {integrity: sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==} engines: {node: '>=0.4.7'} hasBin: true @@ -2319,16 +2360,16 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.9: - resolution: {integrity: sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==} + ignore@7.0.12: + resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==} engines: {node: '>= 4'} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-fresh@4.0.0: - resolution: {integrity: sha512-Fpi660c7VPDM3fPKYovStd9IP1CPOikf6v/dGxJJMmHPcwYQIMJ4W7kO1avBYEpMqkCh+Dx3Ln6H7VYqgztLjw==} + import-fresh@4.0.1: + resolution: {integrity: sha512-/XFeXYgkuWgbtqPC7qPKvciXTvhtz7WMNQKQsAM1Kz+j0I5OWJDrybfiya6QtHF9oEvPkLwTHmC9+4OR/MmfIA==} engines: {node: '>=22.15'} import-meta-resolve@4.2.0: @@ -2461,13 +2502,13 @@ packages: resolution: {integrity: sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==} hasBin: true - jsdoc-type-pratt-parser@9.2.2: - resolution: {integrity: sha512-eFY7Rlscvv/BpY6UsVsjKwMhu1tJO/F4U/vC9g3yeRy/JqszuO1UP6Vdq+85G+3FP6fEw/sWR8a5ftPFY86csw==} + jsdoc-type-pratt-parser@9.2.3: + resolution: {integrity: sha512-xsCb5EbmLWNlQ8v5zQIgSYSyf1rSyVPjh8p9G2xviOl1weHm8a611fHWUOGn7ISquCL6LoBKZvh0tcKmpVGgzw==} engines: {node: ^22.22.2 || >=24.15.0} - jsep@1.4.0: - resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} - engines: {node: '>= 10.16.0'} + jsep@1.6.0: + resolution: {integrity: sha512-Y8Tr54CX7B5YGAwo7/n8eumidEX3Opx98RyyQVnlHqUaZcPA8uUzL2v1rshr9ypW9Q6hNjfJtg3sL+8kaNE7TA==} + engines: {node: '>= 20.16.0'} jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} @@ -2621,8 +2662,8 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - magic-string@1.4.1: - resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==} + magic-string@1.4.3: + resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} magicast@0.5.5: resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} @@ -2635,8 +2676,8 @@ packages: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} - markdown-it@14.3.2: - resolution: {integrity: sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q==} + markdown-it@14.3.3: + resolution: {integrity: sha512-L//tDAkjTjJCU6pBUSBTgs3Kg4InVaZuUZog/b6V12UKhcDQ7S78877ibbCanRExwPuYP1CZD7Gd2dxSy1GXRg==} hasBin: true markdown-it@15.0.1: @@ -2657,8 +2698,8 @@ packages: resolution: {integrity: sha512-qHKeU2E1bdyNAT077go2FVTNXvYcktN5IHtF6XyeD1l0PClxzSp2tUApAV14ORI8DGX4H9bNKZEzelZp4qn8IA==} engines: {node: '>=22'} - mdn-data@2.35.0: - resolution: {integrity: sha512-k+1+dEIm2z/BEfdvYzT/fIyAGeDBo/1AJNJI4ilFXJRYoxP/3Ds+GP8shAOIq8jYXY9N5nGB3Ag104NsagmLAA==} + mdn-data@2.37.2: + resolution: {integrity: sha512-rz9YOBGItdSbe7HcSArUbhiAv4SB/4DKRYg96CSFS2CcIYMbUvBtZlzA1NCbk2AdgbmYwMqwNblLpR441lLeiQ==} mdurl@2.1.0: resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} @@ -2703,8 +2744,8 @@ packages: micromark-factory-label@2.0.1: resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} @@ -2796,8 +2837,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.19: - resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2815,8 +2856,8 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.56: - resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==} + node-releases@2.0.59: + resolution: {integrity: sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==} engines: {node: '>=18'} nodemon@3.1.14: @@ -2878,8 +2919,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.8.0: - resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + package-manager-detector@1.9.0: + resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -2935,8 +2976,8 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - postcss@8.5.28: - resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + postcss@8.5.30: + resolution: {integrity: sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3011,8 +3052,8 @@ packages: reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - regjsparser@0.13.3: - resolution: {integrity: sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==} + regjsparser@0.13.4: + resolution: {integrity: sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==} hasBin: true release-please@17.11.2: @@ -3043,8 +3084,8 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + resolve@1.22.13: + resolution: {integrity: sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==} engines: {node: '>= 0.4'} hasBin: true @@ -3064,8 +3105,8 @@ packages: engines: {node: 20 || >=22} hasBin: true - rolldown@1.2.9: - resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} + rolldown@1.2.14: + resolution: {integrity: sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3118,6 +3159,10 @@ packages: resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} + smol-toml@1.9.1: + resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} + engines: {node: '>= 18'} + socket.io-client@4.8.3: resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} @@ -3126,8 +3171,8 @@ packages: resolution: {integrity: sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==} engines: {node: '>=10.0.0'} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} source-map@0.6.1: @@ -3159,8 +3204,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -3174,8 +3219,8 @@ packages: resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} - string-width@8.2.2: - resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} engines: {node: '>=20'} string_decoder@0.10.31: @@ -3246,8 +3291,8 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.1: - resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -3344,8 +3389,8 @@ packages: undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} - unicorn-magic@0.4.0: - resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + unicorn-magic@0.4.1: + resolution: {integrity: sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==} engines: {node: '>=20'} unist-util-is@4.1.0: @@ -3367,8 +3412,8 @@ packages: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} - update-browserslist-db@1.3.3: - resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==} + update-browserslist-db@1.3.4: + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -3382,8 +3427,12 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vite@8.3.0: - resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} + verkit@0.5.0: + resolution: {integrity: sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==} + engines: {node: '>=18.12.0'} + + vite@8.3.4: + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3466,8 +3515,8 @@ packages: jsdom: optional: true - vscode-languageserver-textdocument@1.0.14: - resolution: {integrity: sha512-EQyqJMi552E4ZTf46izQ4Fj6XquqxCySR3J5ZSD1SisMf6RfpeOWHxGBE8Gr6V0/3GHIGdAzDn8F8+1nTGCnoQ==} + vscode-languageserver-textdocument@1.0.15: + resolution: {integrity: sha512-Gsfr9S8LU3S6885cS+NaleasJ0n3V5y/SOTqHCifQiXWFw3G9w9/YItDfwHrlCz+dSZWWZ8ilYVMcJe7Sz7Sxw==} vscode-uri@3.2.0: resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} @@ -3579,6 +3628,167 @@ packages: snapshots: + '@3sixty/api-core@0.20.0-next.0(supports-color@10.2.2)': + dependencies: + '@3sixty/api-models': 0.20.0-next.0 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/web': 0.20.0-next.1 + socket.io-client: 4.8.3(supports-color@10.2.2) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@3sixty/api-models@0.20.0-next.0': + dependencies: + '@3sixty/context': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/entity': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/web': 0.20.0-next.1 + + '@3sixty/background-task-models@0.20.0-next.0': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/entity': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/cli-core@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + chalk: 6.0.0 + commander: 15.0.0 + dotenv: 18.0.2 + + '@3sixty/context@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/core@0.20.0-next.1': + dependencies: + '@3sixty/nameof': 0.20.0-next.1 + intl-messageformat: 12.1.2 + rfc6902: 5.3.0 + + '@3sixty/crypto@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@noble/ciphers': 2.4.0 + '@noble/curves': 2.4.0 + '@noble/hashes': 2.4.0 + '@scure/base': 2.4.0 + '@scure/bip32': 2.4.0 + '@scure/bip39': 2.4.0 + micro-key-producer: 0.10.2 + + '@3sixty/entity-storage-connector-memory@0.20.0-next.0': + dependencies: + '@3sixty/api-models': 0.20.0-next.0 + '@3sixty/context': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/entity': 0.20.0-next.1 + '@3sixty/entity-storage-models': 0.20.0-next.0 + '@3sixty/logging-models': 0.20.0-next.0 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/entity-storage-models@0.20.0-next.0': + dependencies: + '@3sixty/context': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/crypto': 0.20.0-next.1 + '@3sixty/entity': 0.20.0-next.1 + '@3sixty/logging-models': 0.20.0-next.0 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/entity@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + + '@3sixty/logging-models@0.20.0-next.0': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/entity': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/nameof-transformer@0.20.0-next.1': + dependencies: + typescript: 6.0.3 + + '@3sixty/nameof-vitest-plugin@0.20.0-next.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1))': + dependencies: + '@3sixty/nameof-transformer': 0.20.0-next.1 + vitest: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitest/browser-playwright' + - '@vitest/browser-preview' + - '@vitest/browser-webdriverio' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - happy-dom + - jsdom + - msw + - vite + + '@3sixty/nameof@0.20.0-next.1': {} + + '@3sixty/tools-core@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/tools-models': 0.20.0-next.1 + glob: 13.0.6 + typescript: 6.0.3 + + '@3sixty/tools-models@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + + '@3sixty/ts-to-openapi@0.20.0-next.1': + dependencies: + '@3sixty/cli-core': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/tools-core': 0.20.0-next.1 + '@3sixty/tools-models': 0.20.0-next.1 + '@3sixty/web': 0.20.0-next.1 + commander: 15.0.0 + glob: 13.0.6 + + '@3sixty/validate-locales@0.20.0-next.1': + dependencies: + '@3sixty/cli-core': 0.20.0-next.1 + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/nameof-transformer': 0.20.0-next.1 + commander: 15.0.0 + glob: 13.0.6 + typescript: 6.0.3 + + '@3sixty/vault-models@0.20.0-next.0': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + '@3sixty/web': 0.20.0-next.1 + + '@3sixty/web@0.20.0-next.1': + dependencies: + '@3sixty/core': 0.20.0-next.1 + '@3sixty/crypto': 0.20.0-next.1 + '@3sixty/nameof': 0.20.0-next.1 + jose: 6.2.12 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -3612,13 +3822,13 @@ snapshots: hashery: 1.5.1 keyv: 5.6.0 - '@commitlint/cli@21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.2)(typescript@6.0.3)': + '@commitlint/cli@21.2.3(@types/node@26.6.2)(conventional-commits-parser@7.1.3)(typescript@6.0.3)': dependencies: '@commitlint/config-conventional': 21.2.3 '@commitlint/format': 21.2.3 '@commitlint/lint': 21.2.3 '@commitlint/load': 21.2.3(@types/node@26.6.2)(typescript@6.0.3) - '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.2) + '@commitlint/read': 21.2.3(conventional-commits-parser@7.1.3) '@commitlint/types': 21.2.3 tinyexec: 1.3.1 yargs: 18.2.0 @@ -3631,7 +3841,7 @@ snapshots: '@commitlint/config-conventional@21.2.3': dependencies: '@commitlint/types': 21.2.3 - conventional-changelog-conventionalcommits: 10.4.0 + conventional-changelog-conventionalcommits: 10.4.1 '@commitlint/config-validator@21.2.3': dependencies: @@ -3683,13 +3893,13 @@ snapshots: dependencies: '@commitlint/types': 21.2.3 conventional-changelog-angular: 9.4.0 - conventional-commits-parser: 7.1.2 + conventional-commits-parser: 7.1.3 - '@commitlint/read@21.2.3(conventional-commits-parser@7.1.2)': + '@commitlint/read@21.2.3(conventional-commits-parser@7.1.3)': dependencies: '@commitlint/top-level': 21.2.0 '@commitlint/types': 21.2.3 - '@conventional-changelog/git-client': 3.1.2(conventional-commits-parser@7.1.2) + '@conventional-changelog/git-client': 3.2.0(conventional-commits-parser@7.1.3) tinyexec: 1.3.1 transitivePeerDependencies: - conventional-commits-filter @@ -3718,16 +3928,16 @@ snapshots: '@commitlint/types@21.2.3': dependencies: - conventional-commits-parser: 7.1.2 + conventional-commits-parser: 7.1.3 picocolors: 1.1.1 - '@conventional-changelog/git-client@3.1.2(conventional-commits-parser@7.1.2)': + '@conventional-changelog/git-client@3.2.0(conventional-commits-parser@7.1.3)': dependencies: '@simple-libs/child-process-utils': 2.0.0 '@simple-libs/stream-utils': 2.0.0 - semver: 7.8.5 + verkit: 0.5.0 optionalDependencies: - conventional-commits-parser: 7.1.2 + conventional-commits-parser: 7.1.3 '@conventional-changelog/template@1.4.0': {} @@ -3743,25 +3953,25 @@ snapshots: '@cspell/dict-aws': 4.0.17 '@cspell/dict-bash': 4.2.3 '@cspell/dict-companies': 3.2.13 - '@cspell/dict-cpp': 7.1.2 + '@cspell/dict-cpp': 7.1.3 '@cspell/dict-cryptocurrencies': 5.0.5 '@cspell/dict-csharp': 4.0.8 '@cspell/dict-css': 4.1.2 '@cspell/dict-dart': 2.3.2 '@cspell/dict-data-science': 2.0.16 - '@cspell/dict-django': 4.1.6 + '@cspell/dict-django': 4.1.7 '@cspell/dict-docker': 1.1.17 '@cspell/dict-dotnet': 5.0.15 '@cspell/dict-elixir': 4.0.8 - '@cspell/dict-en-common-misspellings': 2.2.2 - '@cspell/dict-en-gb-mit': 3.1.29 - '@cspell/dict-en_us': 4.4.40 + '@cspell/dict-en-common-misspellings': 2.2.5 + '@cspell/dict-en-gb-mit': 3.1.30 + '@cspell/dict-en_us': 4.4.41 '@cspell/dict-filetypes': 3.0.18 '@cspell/dict-flutter': 1.1.1 '@cspell/dict-fonts': 4.0.6 '@cspell/dict-fsharp': 1.1.1 '@cspell/dict-fullstack': 3.2.9 - '@cspell/dict-gaming-terms': 1.1.2 + '@cspell/dict-gaming-terms': 1.1.3 '@cspell/dict-git': 3.1.1 '@cspell/dict-golang': 6.0.29 '@cspell/dict-google': 1.0.9 @@ -3779,17 +3989,17 @@ snapshots: '@cspell/dict-markdown': 2.0.18(@cspell/dict-css@4.1.2)(@cspell/dict-html-symbol-entities@4.0.5)(@cspell/dict-html@4.0.16)(@cspell/dict-typescript@3.2.3) '@cspell/dict-monkeyc': 1.1.2 '@cspell/dict-node': 5.0.9 - '@cspell/dict-npm': 5.2.50 + '@cspell/dict-npm': 5.2.54 '@cspell/dict-php': 4.1.3 '@cspell/dict-powershell': 5.0.17 - '@cspell/dict-public-licenses': 2.0.16 + '@cspell/dict-public-licenses': 2.0.17 '@cspell/dict-python': 4.5.2 '@cspell/dict-r': 2.1.1 '@cspell/dict-ruby': 5.1.4 '@cspell/dict-rust': 4.1.2 '@cspell/dict-scala': 5.0.11 '@cspell/dict-shell': 1.2.0 - '@cspell/dict-software-terms': 5.4.5 + '@cspell/dict-software-terms': 5.4.8 '@cspell/dict-sql': 2.2.1 '@cspell/dict-svelte': 1.0.7 '@cspell/dict-swift': 2.0.6 @@ -3830,7 +4040,7 @@ snapshots: '@cspell/dict-companies@3.2.13': {} - '@cspell/dict-cpp@7.1.2': {} + '@cspell/dict-cpp@7.1.3': {} '@cspell/dict-cryptocurrencies@5.0.5': {} @@ -3842,7 +4052,7 @@ snapshots: '@cspell/dict-data-science@2.0.16': {} - '@cspell/dict-django@4.1.6': {} + '@cspell/dict-django@4.1.7': {} '@cspell/dict-docker@1.1.17': {} @@ -3850,11 +4060,11 @@ snapshots: '@cspell/dict-elixir@4.0.8': {} - '@cspell/dict-en-common-misspellings@2.2.2': {} + '@cspell/dict-en-common-misspellings@2.2.5': {} - '@cspell/dict-en-gb-mit@3.1.29': {} + '@cspell/dict-en-gb-mit@3.1.30': {} - '@cspell/dict-en_us@4.4.40': {} + '@cspell/dict-en_us@4.4.41': {} '@cspell/dict-filetypes@3.0.18': {} @@ -3866,7 +4076,7 @@ snapshots: '@cspell/dict-fullstack@3.2.9': {} - '@cspell/dict-gaming-terms@1.1.2': {} + '@cspell/dict-gaming-terms@1.1.3': {} '@cspell/dict-git@3.1.1': {} @@ -3907,13 +4117,13 @@ snapshots: '@cspell/dict-node@5.0.9': {} - '@cspell/dict-npm@5.2.50': {} + '@cspell/dict-npm@5.2.54': {} '@cspell/dict-php@4.1.3': {} '@cspell/dict-powershell@5.0.17': {} - '@cspell/dict-public-licenses@2.0.16': {} + '@cspell/dict-public-licenses@2.0.17': {} '@cspell/dict-python@4.5.2': dependencies: @@ -3929,7 +4139,7 @@ snapshots: '@cspell/dict-shell@1.2.0': {} - '@cspell/dict-software-terms@5.4.5': {} + '@cspell/dict-software-terms@5.4.8': {} '@cspell/dict-sql@2.2.1': {} @@ -3977,10 +4187,10 @@ snapshots: '@es-joy/jsdoccomment@0.98.0': dependencies: '@types/estree': 1.0.9 - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/types': 8.71.2 comment-parser: 1.4.9 esquery: 1.7.0 - jsdoc-type-pratt-parser: 9.2.2 + jsdoc-type-pratt-parser: 9.2.3 '@es-joy/resolve.exports@1.2.0': {} @@ -4007,10 +4217,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.1.1': + '@eslint/css-tree@4.1.2': dependencies: - mdn-data: 2.35.0 - source-map-js: 1.2.1 + mdn-data: 2.37.2 + source-map-js: 1.2.2 '@eslint/eslintrc@3.3.7(supports-color@10.2.2)': dependencies: @@ -4085,13 +4295,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 - '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': + '@jsep-plugin/assignment@1.4.0(jsep@1.6.0)': dependencies: - jsep: 1.4.0 + jsep: 1.6.0 - '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': + '@jsep-plugin/regex@1.1.0(jsep@1.6.0)': dependencies: - jsep: 1.4.0 + jsep: 1.6.0 '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: @@ -4101,7 +4311,7 @@ snapshots: '@keyv/serialize@1.1.1': {} - '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 @@ -4191,7 +4401,7 @@ snapshots: dependencies: '@octokit/openapi-types': 24.2.0 - '@oxc-project/types@0.150.0': {} + '@oxc-project/types@0.154.0': {} '@pkgr/core@0.3.6': {} @@ -4199,49 +4409,49 @@ snapshots: dependencies: tinyexec: 1.3.1 - '@rolldown/binding-android-arm-eabi@1.2.9': + '@rolldown/binding-android-arm-eabi@1.2.14': optional: true - '@rolldown/binding-android-arm64@1.2.9': + '@rolldown/binding-android-arm64@1.2.14': optional: true - '@rolldown/binding-darwin-arm64@1.2.9': + '@rolldown/binding-darwin-arm64@1.2.14': optional: true - '@rolldown/binding-darwin-x64@1.2.9': + '@rolldown/binding-darwin-x64@1.2.14': optional: true - '@rolldown/binding-freebsd-x64@1.2.9': + '@rolldown/binding-freebsd-x64@1.2.14': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.9': + '@rolldown/binding-linux-arm64-gnu@1.2.14': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.9': + '@rolldown/binding-linux-arm64-musl@1.2.14': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.9': + '@rolldown/binding-linux-ppc64-gnu@1.2.14': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.9': + '@rolldown/binding-linux-s390x-gnu@1.2.14': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.9': + '@rolldown/binding-linux-x64-gnu@1.2.14': optional: true - '@rolldown/binding-linux-x64-musl@1.2.9': + '@rolldown/binding-linux-x64-musl@1.2.14': optional: true - '@rolldown/binding-openharmony-arm64@1.2.9': + '@rolldown/binding-openharmony-arm64@1.2.14': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.9': + '@rolldown/binding-win32-arm64-msvc@1.2.14': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.9': + '@rolldown/binding-win32-x64-msvc@1.2.14': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -4293,7 +4503,7 @@ snapshots: '@stylistic/eslint-plugin@5.10.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)) - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/types': 8.71.2 eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -4304,167 +4514,6 @@ snapshots: dependencies: eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) - '@twin.org/api-core@0.10.1-next.5(supports-color@10.2.2)': - dependencies: - '@twin.org/api-models': 0.10.1-next.5 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/web': 0.10.1-next.10 - socket.io-client: 4.8.3(supports-color@10.2.2) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@twin.org/api-models@0.10.1-next.5': - dependencies: - '@twin.org/context': 0.10.1-next.10 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/entity': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/web': 0.10.1-next.10 - - '@twin.org/background-task-models@0.10.1-next.1': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/entity': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/cli-core@0.10.1-next.10': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - chalk: 6.0.0 - commander: 15.0.0 - dotenv: 18.0.1 - - '@twin.org/context@0.10.1-next.10': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/core@0.10.1-next.10': - dependencies: - '@twin.org/nameof': 0.10.1-next.10 - intl-messageformat: 12.1.2 - rfc6902: 5.3.0 - - '@twin.org/crypto@0.10.1-next.10': - dependencies: - '@noble/ciphers': 2.4.0 - '@noble/curves': 2.4.0 - '@noble/hashes': 2.4.0 - '@scure/base': 2.4.0 - '@scure/bip32': 2.4.0 - '@scure/bip39': 2.4.0 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - micro-key-producer: 0.10.2 - - '@twin.org/entity-storage-connector-memory@0.10.1-next.9': - dependencies: - '@twin.org/api-models': 0.10.1-next.5 - '@twin.org/context': 0.10.1-next.10 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/entity': 0.10.1-next.10 - '@twin.org/entity-storage-models': 0.10.1-next.9 - '@twin.org/logging-models': 0.10.1-next.2 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/entity-storage-models@0.10.1-next.9': - dependencies: - '@twin.org/context': 0.10.1-next.10 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/crypto': 0.10.1-next.10 - '@twin.org/entity': 0.10.1-next.10 - '@twin.org/logging-models': 0.10.1-next.2 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/entity@0.10.1-next.10': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - reflect-metadata: 0.2.2 - tslib: 2.8.1 - - '@twin.org/logging-models@0.10.1-next.2': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/entity': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/nameof-transformer@0.10.1-next.10': - dependencies: - typescript: 6.0.3 - - '@twin.org/nameof-vitest-plugin@0.10.1-next.10(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1))': - dependencies: - '@twin.org/nameof-transformer': 0.10.1-next.10 - vitest: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@opentelemetry/api' - - '@types/node' - - '@vitest/browser-playwright' - - '@vitest/browser-preview' - - '@vitest/browser-webdriverio' - - '@vitest/coverage-istanbul' - - '@vitest/coverage-v8' - - '@vitest/ui' - - happy-dom - - jsdom - - msw - - vite - - '@twin.org/nameof@0.10.1-next.10': {} - - '@twin.org/tools-core@0.9.3-next.1': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/tools-models': 0.9.3-next.1 - glob: 13.0.6 - typescript: 6.0.3 - - '@twin.org/tools-models@0.9.3-next.1': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - - '@twin.org/ts-to-openapi@0.9.3-next.1': - dependencies: - '@twin.org/cli-core': 0.10.1-next.10 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/tools-core': 0.9.3-next.1 - '@twin.org/tools-models': 0.9.3-next.1 - '@twin.org/web': 0.10.1-next.10 - commander: 15.0.0 - glob: 13.0.6 - - '@twin.org/validate-locales@0.10.1-next.10': - dependencies: - '@twin.org/cli-core': 0.10.1-next.10 - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/nameof-transformer': 0.10.1-next.10 - commander: 15.0.0 - glob: 13.0.6 - typescript: 6.0.3 - - '@twin.org/vault-models@0.10.1-next.3': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - '@twin.org/web': 0.10.1-next.10 - - '@twin.org/web@0.10.1-next.10': - dependencies: - '@twin.org/core': 0.10.1-next.10 - '@twin.org/crypto': 0.10.1-next.10 - '@twin.org/nameof': 0.10.1-next.10 - jose: 6.2.12 - '@tybys/wasm-util@0.10.4': dependencies: tslib: 2.8.1 @@ -4518,7 +4567,7 @@ snapshots: '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.70.1 eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) - ignore: 7.0.9 + ignore: 7.0.12 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 @@ -4546,15 +4595,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.71.2(supports-color@10.2.2)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.71.2(typescript@6.0.3) + '@typescript-eslint/types': 8.71.2 + debug: 4.4.3(supports-color@10.2.2) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.70.1': dependencies: '@typescript-eslint/types': 8.70.1 '@typescript-eslint/visitor-keys': 8.70.1 + '@typescript-eslint/scope-manager@8.71.2': + dependencies: + '@typescript-eslint/types': 8.71.2 + '@typescript-eslint/visitor-keys': 8.71.2 + '@typescript-eslint/tsconfig-utils@8.70.1(typescript@6.0.3)': dependencies: typescript: 6.0.3 + '@typescript-eslint/tsconfig-utils@8.71.2(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + '@typescript-eslint/type-utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.70.1 @@ -4569,6 +4636,8 @@ snapshots: '@typescript-eslint/types@8.70.1': {} + '@typescript-eslint/types@8.71.2': {} + '@typescript-eslint/typescript-estree@8.70.1(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@typescript-eslint/project-service': 8.70.1(supports-color@10.2.2)(typescript@6.0.3) @@ -4584,6 +4653,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.71.2(supports-color@10.2.2)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.71.2(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.71.2(typescript@6.0.3) + '@typescript-eslint/types': 8.71.2 + '@typescript-eslint/visitor-keys': 8.71.2 + debug: 4.4.3(supports-color@10.2.2) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)) @@ -4595,11 +4679,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.71.2(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)) + '@typescript-eslint/scope-manager': 8.71.2 + '@typescript-eslint/types': 8.71.2 + '@typescript-eslint/typescript-estree': 8.71.2(supports-color@10.2.2)(typescript@6.0.3) + eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.70.1': dependencies: '@typescript-eslint/types': 8.70.1 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.71.2': + dependencies: + '@typescript-eslint/types': 8.71.2 + eslint-visitor-keys: 5.0.1 + '@unrs/resolver-binding-android-arm-eabi@1.12.2': optional: true @@ -4658,7 +4758,7 @@ snapshots: dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@napi-rs/wasm-runtime': 1.2.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': @@ -4673,39 +4773,39 @@ snapshots: '@vitest/coverage-v8@5.0.1(vitest@5.0.1)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/istanbul-lib-coverage': 1.0.1 - '@vitest/istanbul-lib-report': 1.0.1 + '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-report': 1.0.2 ast-v8-to-istanbul: 1.0.7 magicast: 0.5.5 obug: 2.2.1 - std-env: 4.2.0 - tinyrainbow: 3.1.1 - vitest: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) - '@vitest/istanbul-lib-coverage@1.0.1': {} + '@vitest/istanbul-lib-coverage@1.0.2': {} - '@vitest/istanbul-lib-report@1.0.1': + '@vitest/istanbul-lib-report@1.0.2': dependencies: - '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1))': + '@vitest/mocker@5.0.1(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.1 estree-walker: 3.0.3 - magic-string: 1.4.1 + magic-string: 1.4.3 optionalDependencies: - vite: 8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1) + vite: 8.3.4(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1) '@vitest/spy@5.0.1': {} '@xmldom/xmldom@0.8.15': {} - acorn-jsx@5.3.2(acorn@8.18.0): + acorn-jsx@5.3.2(acorn@8.19.0): dependencies: - acorn: 8.18.0 + acorn: 8.19.0 - acorn@8.18.0: {} + acorn@8.19.0: {} agent-base@7.1.4: {} @@ -4725,7 +4825,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.3.0: {} + ansi-regex@6.4.0: {} ansi-styles@4.3.0: dependencies: @@ -4742,7 +4842,7 @@ snapshots: argparse@2.0.1: {} - argparse@3.0.2: {} + argparse@3.0.3: {} argue-cli@3.2.0: {} @@ -4768,7 +4868,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.25: {} + baseline-browser-mapping@2.11.29: {} before-after-hook@2.2.3: {} @@ -4789,13 +4889,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.29.0: + browserslist@4.29.3: dependencies: - baseline-browser-mapping: 2.11.25 - caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.433 - node-releases: 2.0.56 - update-browserslist-db: 1.3.3(browserslist@4.29.0) + baseline-browser-mapping: 2.11.29 + caniuse-lite: 1.0.30001816 + electron-to-chromium: 1.5.459 + node-releases: 2.0.59 + update-browserslist-db: 1.3.4(browserslist@4.29.3) builtin-modules@5.4.0: {} @@ -4817,11 +4917,11 @@ snapshots: camelcase@5.3.1: {} - caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001816: {} - chai@6.2.2: {} + chai@6.3.0: {} - chalk-template@1.1.2: + chalk-template@1.1.3: dependencies: chalk: 5.6.2 @@ -4834,6 +4934,8 @@ snapshots: chalk@6.0.0: {} + chalk@6.0.1: {} + change-case@5.4.4: {} character-entities-legacy@3.0.0: {} @@ -4911,7 +5013,7 @@ snapshots: dependencies: '@conventional-changelog/template': 1.4.0 - conventional-changelog-conventionalcommits@10.4.0: + conventional-changelog-conventionalcommits@10.4.1: dependencies: '@conventional-changelog/template': 1.4.0 @@ -4923,7 +5025,7 @@ snapshots: dependencies: conventional-commits-filter: 3.0.0 dateformat: 3.0.3 - handlebars: 4.7.9 + handlebars: 4.7.10 json-stringify-safe: 5.0.1 meow: 8.1.2 semver: 7.8.5 @@ -4934,7 +5036,7 @@ snapshots: lodash.ismatch: 4.4.0 modify-values: 1.0.1 - conventional-commits-parser@7.1.2: + conventional-commits-parser@7.1.3: dependencies: '@simple-libs/stream-utils': 2.0.0 argue-cli: 3.2.0 @@ -4951,7 +5053,7 @@ snapshots: core-js-compat@3.50.0: dependencies: - browserslist: 4.29.0 + browserslist: 4.29.3 core-util-is@1.0.3: {} @@ -4981,7 +5083,7 @@ snapshots: dependencies: '@cspell/cspell-types': 10.3.3 comment-json: 5.0.0 - smol-toml: 1.8.0 + smol-toml: 1.9.1 yaml: 2.9.1 cspell-dictionary@10.3.3: @@ -4990,7 +5092,7 @@ snapshots: '@cspell/cspell-pipe': 10.3.3 '@cspell/cspell-types': 10.3.3 cspell-trie-lib: 10.3.3(@cspell/cspell-types@10.3.3) - fast-equals: 6.0.4 + fast-equals: 6.2.1 cspell-gitignore@10.3.3: dependencies: @@ -5033,9 +5135,9 @@ snapshots: cspell-trie-lib: 10.3.3(@cspell/cspell-types@10.3.3) env-paths: 4.0.0 gensequence: 8.0.8 - import-fresh: 4.0.0 + import-fresh: 4.0.1 resolve-from: 5.0.0 - vscode-languageserver-textdocument: 1.0.14 + vscode-languageserver-textdocument: 1.0.15 vscode-uri: 3.2.0 xdg-basedir: 5.1.0 @@ -5052,9 +5154,9 @@ snapshots: '@cspell/cspell-worker': 10.3.3 '@cspell/dynamic-import': 10.3.3 '@cspell/url': 10.3.3 - ansi-regex: 6.3.0 - chalk: 6.0.0 - chalk-template: 1.1.2 + ansi-regex: 6.4.0 + chalk: 6.0.1 + chalk-template: 1.1.3 commander: 15.0.0 cspell-config-lib: 10.3.3 cspell-dictionary: 10.3.3 @@ -5141,15 +5243,15 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv@18.0.1: {} + dotenv@18.0.2: {} - electron-to-chromium@1.5.433: {} + electron-to-chromium@1.5.459: {} emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - engine.io-client@6.6.6(supports-color@10.2.2): + engine.io-client@6.6.7(supports-color@10.2.2): dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3(supports-color@10.2.2) @@ -5202,9 +5304,9 @@ snapshots: optionalDependencies: unrs-resolver: 1.12.2 - eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2): + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.71.2(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2): dependencies: - '@typescript-eslint/types': 8.70.1 + '@typescript-eslint/types': 8.71.2 comment-parser: 1.4.9 debug: 4.4.3(supports-color@10.2.2) eslint: 10.11.0(jiti@2.6.1)(supports-color@10.2.2) @@ -5215,7 +5317,7 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.2(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) transitivePeerDependencies: - supports-color @@ -5223,7 +5325,7 @@ snapshots: dependencies: '@es-joy/jsdoccomment': 0.98.0 '@es-joy/resolve.exports': 1.2.0 - '@typescript-eslint/utils': 8.70.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.71.2(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) are-docs-informative: 0.1.1 comment-parser: 1.4.9 debug: 4.4.3(supports-color@10.2.2) @@ -5263,8 +5365,8 @@ snapshots: eslint-plugin-unicorn@76.0.0(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.11.0(jiti@2.6.1)(supports-color@10.2.2)) - '@eslint/css-tree': 4.1.1 - browserslist: 4.29.0 + '@eslint/css-tree': 4.1.2 + browserslist: 4.29.3 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.50.0 @@ -5278,7 +5380,7 @@ snapshots: is-builtin-module: 5.0.0 pluralize: 8.0.0 quote-js-string: 0.1.0 - regjsparser: 0.13.3 + regjsparser: 0.13.4 reserved-identifiers: 1.2.0 semver: 7.8.5 strip-indent: 4.1.1 @@ -5342,14 +5444,14 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.18.0 - acorn-jsx: 5.3.2(acorn@8.18.0) + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) eslint-visitor-keys: 4.2.1 espree@11.2.0: dependencies: - acorn: 8.18.0 - acorn-jsx: 5.3.2(acorn@8.18.0) + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) eslint-visitor-keys: 5.0.1 esprima@4.0.1: {} @@ -5376,7 +5478,7 @@ snapshots: fast-diff@1.3.0: {} - fast-equals@6.0.4: {} + fast-equals@6.2.1: {} fast-glob@3.3.3: dependencies: @@ -5490,13 +5592,13 @@ snapshots: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 - ignore: 7.0.9 + ignore: 7.0.12 is-path-inside: 4.0.0 micromatch: 4.0.8 slash: 5.1.0 - unicorn-magic: 0.4.0 + unicorn-magic: 0.4.1 - handlebars@4.7.9: + handlebars@4.7.10: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -5555,14 +5657,14 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.9: {} + ignore@7.0.12: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-fresh@4.0.0: {} + import-fresh@4.0.1: {} import-meta-resolve@4.2.0: {} @@ -5657,12 +5759,11 @@ snapshots: dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@9.2.2: + jsdoc-type-pratt-parser@9.2.3: dependencies: '@types/estree': 1.0.9 - '@types/node': 26.6.2 - jsep@1.4.0: {} + jsep@1.6.0: {} jsesc@3.1.0: {} @@ -5680,9 +5781,9 @@ snapshots: jsonpath-plus@10.4.0: dependencies: - '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) - '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) - jsep: 1.4.0 + '@jsep-plugin/assignment': 1.4.0(jsep@1.6.0) + '@jsep-plugin/regex': 1.1.0(jsep@1.6.0) + jsep: 1.6.0 jsonpointer@5.0.1: {} @@ -5778,7 +5879,7 @@ snapshots: lunr@2.3.9: {} - magic-string@1.4.1: + magic-string@1.4.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -5786,13 +5887,13 @@ snapshots: dependencies: '@babel/parser': 7.29.9 '@babel/types': 7.29.8 - source-map-js: 1.2.1 + source-map-js: 1.2.2 map-obj@1.0.1: {} map-obj@4.3.0: {} - markdown-it@14.3.2: + markdown-it@14.3.3: dependencies: argparse: 2.0.1 entities: 4.5.0 @@ -5803,7 +5904,7 @@ snapshots: markdown-it@15.0.1: dependencies: - argparse: 3.0.2 + argparse: 3.0.3 entities: 8.1.0 linkify-it: 6.1.0 mdurl: 2.1.0 @@ -5842,7 +5943,7 @@ snapshots: transitivePeerDependencies: - supports-color - mdn-data@2.35.0: {} + mdn-data@2.37.2: {} mdurl@2.1.0: {} @@ -5880,7 +5981,7 @@ snapshots: devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-factory-title: 2.0.1 micromark-factory-whitespace: 2.0.1 micromark-util-character: 2.1.1 @@ -5896,7 +5997,7 @@ snapshots: micromark-extension-directive@4.0.0: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-factory-whitespace: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 @@ -5914,7 +6015,7 @@ snapshots: dependencies: devlop: 1.1.0 micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-sanitize-uri: 2.0.1 @@ -5924,7 +6025,7 @@ snapshots: micromark-extension-gfm-table@2.1.1: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 @@ -5934,7 +6035,7 @@ snapshots: '@types/katex': 0.16.8 devlop: 1.1.0 katex: 0.16.47 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 @@ -5952,21 +6053,21 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-factory-space@2.0.1: + micromark-factory-space@2.1.0: dependencies: micromark-util-character: 2.1.1 micromark-util-types: 2.0.2 micromark-factory-title@2.0.1: dependencies: - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 micromark-factory-whitespace@2.0.1: dependencies: - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 @@ -6031,7 +6132,7 @@ snapshots: decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-chunked: 2.0.1 micromark-util-combine-extensions: 2.0.1 @@ -6079,7 +6180,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.19: {} + nanoid@3.3.20: {} napi-postinstall@0.3.4: {} @@ -6092,7 +6193,7 @@ snapshots: css-select: 5.2.2 he: 1.2.0 - node-releases@2.0.56: {} + node-releases@2.0.59: {} nodemon@3.1.14: dependencies: @@ -6115,7 +6216,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.12 + resolve: 1.22.13 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -6169,7 +6270,7 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@1.8.0: {} + package-manager-detector@1.9.0: {} parent-module@1.0.1: dependencies: @@ -6221,11 +6322,11 @@ snapshots: pluralize@8.0.0: {} - postcss@8.5.28: + postcss@8.5.30: dependencies: - nanoid: 3.3.19 + nanoid: 3.3.20 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 prelude-ls@1.2.1: {} @@ -6242,7 +6343,7 @@ snapshots: publint@0.3.24: dependencies: '@publint/pack': 0.1.7 - package-manager-detector: 1.8.0 + package-manager-detector: 1.9.0 picocolors: 1.1.1 sade: 1.8.1 @@ -6301,7 +6402,7 @@ snapshots: reflect-metadata@0.2.2: {} - regjsparser@0.13.3: + regjsparser@0.13.4: dependencies: jsesc: 3.1.0 @@ -6353,7 +6454,7 @@ snapshots: resolve-pkg-maps@1.0.0: {} - resolve@1.22.12: + resolve@1.22.13: dependencies: es-errors: 1.3.0 is-core-module: 2.17.0 @@ -6371,26 +6472,26 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 - rolldown@1.2.9: + rolldown@1.2.14: dependencies: - '@oxc-project/types': 0.150.0 + '@oxc-project/types': 0.154.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.9 - '@rolldown/binding-android-arm64': 1.2.9 - '@rolldown/binding-darwin-arm64': 1.2.9 - '@rolldown/binding-darwin-x64': 1.2.9 - '@rolldown/binding-freebsd-x64': 1.2.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.9 - '@rolldown/binding-linux-arm64-gnu': 1.2.9 - '@rolldown/binding-linux-arm64-musl': 1.2.9 - '@rolldown/binding-linux-ppc64-gnu': 1.2.9 - '@rolldown/binding-linux-s390x-gnu': 1.2.9 - '@rolldown/binding-linux-x64-gnu': 1.2.9 - '@rolldown/binding-linux-x64-musl': 1.2.9 - '@rolldown/binding-openharmony-arm64': 1.2.9 - '@rolldown/binding-win32-arm64-msvc': 1.2.9 - '@rolldown/binding-win32-x64-msvc': 1.2.9 + '@rolldown/binding-android-arm-eabi': 1.2.14 + '@rolldown/binding-android-arm64': 1.2.14 + '@rolldown/binding-darwin-arm64': 1.2.14 + '@rolldown/binding-darwin-x64': 1.2.14 + '@rolldown/binding-freebsd-x64': 1.2.14 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.14 + '@rolldown/binding-linux-arm64-gnu': 1.2.14 + '@rolldown/binding-linux-arm64-musl': 1.2.14 + '@rolldown/binding-linux-ppc64-gnu': 1.2.14 + '@rolldown/binding-linux-s390x-gnu': 1.2.14 + '@rolldown/binding-linux-x64-gnu': 1.2.14 + '@rolldown/binding-linux-x64-musl': 1.2.14 + '@rolldown/binding-openharmony-arm64': 1.2.14 + '@rolldown/binding-win32-arm64-msvc': 1.2.14 + '@rolldown/binding-win32-x64-msvc': 1.2.14 run-parallel@1.2.0: dependencies: @@ -6428,11 +6529,13 @@ snapshots: smol-toml@1.8.0: {} + smol-toml@1.9.1: {} + socket.io-client@4.8.3(supports-color@10.2.2): dependencies: '@socket.io/component-emitter': 3.1.2 debug: 4.4.3(supports-color@10.2.2) - engine.io-client: 6.6.6(supports-color@10.2.2) + engine.io-client: 6.6.7(supports-color@10.2.2) socket.io-parser: 4.2.7(supports-color@10.2.2) transitivePeerDependencies: - bufferutil @@ -6446,7 +6549,7 @@ snapshots: transitivePeerDependencies: - supports-color - source-map-js@1.2.1: {} + source-map-js@1.2.2: {} source-map@0.6.1: {} @@ -6477,7 +6580,7 @@ snapshots: stackback@0.0.2: {} - std-env@4.2.0: {} + std-env@4.3.0: {} string-width@4.2.3: dependencies: @@ -6496,7 +6599,7 @@ snapshots: get-east-asian-width: 1.7.0 strip-ansi: 7.2.0 - string-width@8.2.2: + string-width@8.3.0: dependencies: get-east-asian-width: 1.7.0 strip-ansi: 7.2.0 @@ -6513,7 +6616,7 @@ snapshots: strip-ansi@7.2.0: dependencies: - ansi-regex: 6.3.0 + ansi-regex: 6.4.0 strip-indent@3.0.0: dependencies: @@ -6557,7 +6660,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 - tinyrainbow@3.1.1: {} + tinyrainbow@3.2.0: {} to-regex-range@5.0.1: dependencies: @@ -6583,7 +6686,7 @@ snapshots: chalk: 4.1.2 global-prefix: 4.0.0 minimist: 1.2.8 - resolve: 1.22.12 + resolve: 1.22.13 semver: 7.8.5 strip-ansi: 6.0.1 @@ -6609,7 +6712,7 @@ snapshots: dependencies: '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 - markdown-it: 14.3.2 + markdown-it: 14.3.3 minimatch: 10.2.6 typescript: 6.0.3 yaml: 2.9.1 @@ -6629,7 +6732,7 @@ snapshots: undici-types@8.9.0: {} - unicorn-magic@0.4.0: {} + unicorn-magic@0.4.1: {} unist-util-is@4.1.0: {} @@ -6675,9 +6778,9 @@ snapshots: untildify@4.0.0: {} - update-browserslist-db@1.3.3(browserslist@4.29.0): + update-browserslist-db@1.3.4(browserslist@4.29.3): dependencies: - browserslist: 4.29.0 + browserslist: 4.29.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -6692,12 +6795,14 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1): + verkit@0.5.0: {} + + vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.2.9 + postcss: 8.5.30 + rolldown: 1.2.14 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.6.2 @@ -6705,21 +6810,21 @@ snapshots: jiti: 2.6.1 yaml: 2.9.1 - vitest@5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)): + vitest@5.0.1(@types/node@26.6.2)(@vitest/coverage-v8@5.0.1)(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.6.1)) - chai: 6.2.2 + '@vitest/mocker': 5.0.1(vite@8.3.4(@types/node@26.6.2)(jiti@2.6.1)) + chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 1.4.1 + magic-string: 1.4.3 obug: 2.2.1 picomatch: 4.0.7 - std-env: 4.2.0 + std-env: 4.3.0 tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - vite: 8.3.0(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1) + vite: 8.3.4(@types/node@26.6.2)(jiti@2.6.1)(yaml@2.9.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.6.2 @@ -6727,7 +6832,7 @@ snapshots: transitivePeerDependencies: - msw - vscode-languageserver-textdocument@1.0.14: {} + vscode-languageserver-textdocument@1.0.15: {} vscode-uri@3.2.0: {} @@ -6818,7 +6923,7 @@ snapshots: cliui: 9.0.1 escalade: 3.2.0 get-caller-file: 2.0.5 - string-width: 8.2.2 + string-width: 8.3.0 y18n: 5.0.8 yargs-parser: 22.0.0 From d9a7239d8f72691b9f50c35628ccb701c28d389c Mon Sep 17 00:00:00 2001 From: AdrianMSS Date: Mon, 19 Oct 2026 13:09:31 +0200 Subject: [PATCH 4/8] fix: pr reviews #3 --- packages/api-auth-entity-storage-models/src/index.ts | 1 + .../src/models/IAuthenticationAdminComponent.ts | 1 + .../src/models/IAuthenticationComponent.ts | 1 + .../src/models/IAuthenticationUserCreate.ts | 2 +- .../src/models/api/IAdminUserCreateRequest.ts | 1 + .../src/entityStorageAuthenticationAdminRestClient.ts | 1 + .../src/entityStorageAuthenticationRestClient.ts | 1 + .../tests/entityStorageAuthenticationRestClient.spec.ts | 1 + .../src/models/IEntityStorageAuthenticationServiceConfig.ts | 1 + .../IEntityStorageAuthenticationServiceConstructorOptions.ts | 1 + .../src/models/IIdentityAdoption.ts | 2 +- .../src/routes/entityStorageAuthenticationAdminRoutes.ts | 1 + .../src/routes/entityStorageAuthenticationRoutes.ts | 1 + .../src/services/entityStorageAuthenticationAdminService.ts | 1 + .../src/services/entityStorageAuthenticationService.ts | 1 + .../tests/routes/entityStorageAuthenticationRoutes.spec.ts | 2 +- .../services/entityStorageAuthenticationAdminService.spec.ts | 1 + .../tests/services/entityStorageAuthenticationService.spec.ts | 1 + 18 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/api-auth-entity-storage-models/src/index.ts b/packages/api-auth-entity-storage-models/src/index.ts index c13c0fa..8166edf 100644 --- a/packages/api-auth-entity-storage-models/src/index.ts +++ b/packages/api-auth-entity-storage-models/src/index.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. export * from "./models/api/IAdminUserCreateRequest.js"; export * from "./models/api/IAdminUserGetByIdentityRequest.js"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts index 8951a44..1551797 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationAdminComponent.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; import type { IAuthenticationUser } from "./IAuthenticationUser.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 a464f79..b7d1d4c 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts index ac94cf7..71b1bfe 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts @@ -1,4 +1,4 @@ -// Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. /** diff --git a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts index 4960f87..1a3c56a 100644 --- a/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts +++ b/packages/api-auth-entity-storage-models/src/models/api/IAdminUserCreateRequest.ts @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationUserCreate } from "../IAuthenticationUserCreate.js"; diff --git a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts index 2bbdc71..3217035 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationAdminRestClient.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAdminUserCreateRequest, 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 cd82177..713b88b 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationComponent, 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 4cdbcf1..f4ecaa9 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 @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import { GuardError } from "@3sixty/core"; import { HttpMethod } from "@3sixty/web"; 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 c798f13..80c0020 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConfig.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationRateActionConfig } from "@3sixty/api-auth-entity-storage-models"; diff --git a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts index 6fc4b68..2e5e27d 100644 --- a/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts +++ b/packages/api-auth-entity-storage-service/src/models/IEntityStorageAuthenticationServiceConstructorOptions.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IEntityStorageAuthenticationServiceConfig } from "./IEntityStorageAuthenticationServiceConfig.js"; diff --git a/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts b/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts index d2cbb83..37c9860 100644 --- a/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts +++ b/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts @@ -1,4 +1,4 @@ -// Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IComponent } from "@3sixty/core"; 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 73dc07b..9f5c744 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAdminUserCreateRequest, 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 374a722..1eda258 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationComponent, 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 b458db9..8b41aa1 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationAdminComponent, 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 bad08db..9fc0e87 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationRateActionConfig, diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts index 663df44..3a5a12b 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -1,4 +1,4 @@ -// Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IHttpRequestContext, IHttpResponse } from "@3sixty/api-models"; import { ComponentFactory } from "@3sixty/core"; 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 42fa6e1..a408e4f 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,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-entity-storage-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; 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 1c7c41e..ce65a3f 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 @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import type { IAuthenticationAuditComponent, From 50304cf512091d8117d051eda3f06b26eff2f656 Mon Sep 17 00:00:00 2001 From: AdrianMSS Date: Tue, 20 Oct 2026 08:49:24 +0200 Subject: [PATCH 5/8] fix: pr reviews #4 --- .../src/models/IAuthenticationComponent.ts | 9 +- .../src/models/IAuthenticationUser.ts | 10 +- .../src/models/IAuthenticationUserCreate.ts | 4 +- .../entityStorageAuthenticationRestClient.ts | 19 +- ...orageAuthenticationAdminRestClient.spec.ts | 16 ++ ...ityStorageAuthenticationRestClient.spec.ts | 18 +- .../src/entities/authenticationUser.ts | 11 +- .../src/processors/authHeaderProcessor.ts | 7 +- .../entityStorageAuthenticationAdminRoutes.ts | 4 +- .../entityStorageAuthenticationRoutes.ts | 30 +-- ...entityStorageAuthenticationAdminService.ts | 75 +++--- .../entityStorageAuthenticationService.ts | 52 ++--- .../src/utils/passwordHelper.ts | 9 +- .../processors/authHeaderProcessor.spec.ts | 61 ++--- .../entityStorageAuthenticationRoutes.spec.ts | 34 +-- ...yStorageAuthenticationAdminService.spec.ts | 91 +++++++- ...entityStorageAuthenticationService.spec.ts | 217 ++++++++++++------ .../tests/tsconfig.json | 2 +- 18 files changed, 406 insertions(+), 263 deletions(-) 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 b7d1d4c..5d9622a 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationComponent.ts @@ -22,13 +22,10 @@ export interface IAuthenticationComponent extends IComponent { }>; /** - * Replace an e-mail subject with a DID and issue a new token. - * @returns The new token and its expiry. + * Attach a DID to the signed-in account. The token subject stays the account id. + * @returns A promise that resolves when the DID has been stored. */ - adoptIdentity(): Promise<{ - token?: string; - expiry: number; - }>; + adoptIdentity(): Promise; /** * Logout the current user. 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..160d257 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUser.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. /** @@ -11,9 +12,14 @@ export interface IAuthenticationUser { email: string; /** - * The user identity. + * Stable identifier of the account. This is the token subject. */ - userIdentity: string; + userId: string; + + /** + * The DID attached to the account, once one has been adopted. + */ + userIdentity?: string; /** * The users organization. diff --git a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts index 71b1bfe..efa1483 100644 --- a/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts +++ b/packages/api-auth-entity-storage-models/src/models/IAuthenticationUserCreate.ts @@ -3,7 +3,7 @@ /** * Fields accepted when an admin creates a user. - * The identity and the organisation may be omitted. The service fills them. + * The DID and the organisation may be omitted. The service allocates the account id and fills the organisation. */ export interface IAuthenticationUserCreate { /** @@ -22,7 +22,7 @@ export interface IAuthenticationUserCreate { scope: string[]; /** - * Identity subject. When omitted, the service stores the e-mail address until a DID exists. + * DID to store on the account. When omitted, the account is created without one. */ userIdentity?: string; 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 713b88b..5769c4f 100644 --- a/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts +++ b/packages/api-auth-entity-storage-rest-client/src/entityStorageAuthenticationRestClient.ts @@ -96,22 +96,11 @@ export class EntityStorageAuthenticationRestClient } /** - * Replace an e-mail subject with a DID and issue a new token. - * @returns The new token and its expiry. The token is read from the response cookie. + * Attach a DID to the signed-in account. The current session is left in place. + * @returns A promise that resolves when the request has completed. */ - public async adoptIdentity(): Promise<{ - token?: string; - expiry: number; - }> { - const response = await this.fetch("/identity", "POST"); - - return { - token: CookieHelper.getCookieFromHeaders( - response?.headers?.[HeaderTypes.SetCookie], - this._cookieName - ), - expiry: response.body.expiry - }; + public async adoptIdentity(): Promise { + await this.fetch("/identity", "POST"); } /** 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..de07cd8 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 @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import { GuardError } from "@3sixty/core"; import { HttpMethod } from "@3sixty/web"; @@ -72,6 +73,21 @@ describe("EntityStorageAuthenticationAdminRestClient", () => { expect(JSON.parse(options.body)).toEqual(createUser); }); + test("sends a create body without a DID when userIdentity is omitted", async () => { + fetchMock.mockResolvedValueOnce(noContentResponse()); + + const { userIdentity, ...withoutDid } = TEST_USER; + await client.create({ ...withoutDid, password: "MyPassword123!" }); + + const [, options] = fetchMock.mock.calls[0]; + const body = JSON.parse(options.body); + expect(body.email).toBe(TEST_USER.email); + expect(body.password).toBe("MyPassword123!"); + expect(body.scope).toEqual(TEST_USER.scope); + expect(body.userIdentity).toBeUndefined(); + expect(userIdentity).toBe(TEST_USER.userIdentity); + }); + test("resolves without a return value", async () => { fetchMock.mockResolvedValueOnce(noContentResponse()); await expect( 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 f4ecaa9..95bc304 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 @@ -157,7 +157,7 @@ describe("EntityStorageAuthenticationRestClient", () => { describe("adoptIdentity", () => { test("sends POST to /identity with no body", async () => { - fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); + fetchMock.mockResolvedValueOnce(noContentResponse()); await client.adoptIdentity(); @@ -167,20 +167,10 @@ describe("EntityStorageAuthenticationRestClient", () => { expect(options.body).toBeUndefined(); }); - test("returns expiry from the response body", async () => { - fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); - - const result = await client.adoptIdentity(); - - expect(result.expiry).toBe(1722514341067); - }); - - test("returns token as undefined when no Set-Cookie header is present", async () => { - fetchMock.mockResolvedValueOnce(jsonResponse({ expiry: 1722514341067 })); - - const result = await client.adoptIdentity(); + test("resolves without a return value", async () => { + fetchMock.mockResolvedValueOnce(noContentResponse()); - expect(result.token).toBeUndefined(); + await expect(client.adoptIdentity()).resolves.toBeUndefined(); }); }); 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..6acf0d4 100644 --- a/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts +++ b/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import { entity, property } from "@3sixty/entity"; @@ -26,10 +27,16 @@ export class AuthenticationUser { public salt!: string; /** - * The user identity. + * Stable identifier of the account. This is the token subject. */ @property({ type: "string", isSecondary: true }) - public identity!: string; + public userId!: string; + + /** + * The DID attached to the account, once one has been adopted. + */ + @property({ type: "string", isSecondary: true, optional: true }) + public identity?: string; /** * The users organization. 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..77ba131 100644 --- a/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts +++ b/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import { HttpContextIdKeys, @@ -188,10 +189,10 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { // 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") + this._userEntityStorage.get(sub, "userId") ); - if (user?.identity === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) { + if (user?.userId === sub && (passwordVersion ?? 0) === (user.passwordVersion ?? 0)) { validParts.push("user"); } if (user?.organization === org) { @@ -206,7 +207,7 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { // 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.User] = user?.userId; contextIds[ContextIdKeys.UserOrganization] = user?.organization; // If the tenant has a custom public origin, we set it in the context for downstream processors to use. 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 9f5c744..47b607a 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationAdminRoutes.ts @@ -79,7 +79,7 @@ export function generateRestRoutesAuthenticationAdmin( { id: "createUserWithoutIdentityRequestExample", description: - "The request to create a user before a DID exists. The service stores the e-mail as the subject and the context organisation as the organisation.", + "The request to create a user before a DID exists. The service allocates an account id and stores the context organisation as the organisation.", request: { body: { email: "user@example.com", @@ -204,6 +204,7 @@ export function generateRestRoutesAuthenticationAdmin( response: { body: { email: "user@example.com", + userId: "6f1c2c0e-4b7a-4f0e-9c1a-2d3e4f5a6b7c", userIdentity: "did:example:123456789abcdefghi", organizationIdentity: "did:example:123456789abcdefghi", scope: ["scope1", "scope2"] @@ -251,6 +252,7 @@ export function generateRestRoutesAuthenticationAdmin( response: { body: { email: "user@example.com", + userId: "6f1c2c0e-4b7a-4f0e-9c1a-2d3e4f5a6b7c", userIdentity: "did:example:123456789abcdefghi", organizationIdentity: "did:example:123456789abcdefghi", scope: ["scope1", "scope2"] 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 1eda258..28162e5 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -199,7 +199,7 @@ export function generateRestRoutesAuthentication( ] }; - const adoptIdentityRoute: IRestRoute = { + const adoptIdentityRoute: IRestRoute = { operationId: "authenticationAdoptIdentity", summary: "Adopt a DID for the signed-in user", tag: tagsAuthentication[0].name, @@ -209,18 +209,7 @@ export function generateRestRoutesAuthentication( authenticationAdoptIdentity(httpRequestContext, componentName), responseType: [ { - type: nameof(), - examples: [ - { - id: "adoptIdentityResponseExample", - description: "The new session expiry. The token is placed the same way as login.", - response: { - body: { - expiry: 1722514341067 - } - } - } - ] + type: nameof() }, { type: nameof() @@ -265,25 +254,20 @@ export async function authenticationLogin( } /** - * Adopt a DID for the signed-in user and reissue the session. + * Attach a DID to the signed-in account. The current session is left in place. * @param httpRequestContext The request context for the API. * @param componentName The name of the component to use in the routes. - * @returns The response object with the new expiry. The token is not included in the body. + * @returns An empty response once the DID has been stored. */ export async function authenticationAdoptIdentity( httpRequestContext: IHttpRequestContext, componentName: string -): Promise { +): Promise { const component = ComponentFactory.get(componentName); - const result = await component.adoptIdentity(); - - httpRequestContext.processorState.authOperation = "login"; - httpRequestContext.processorState.authToken = result.token; + await component.adoptIdentity(); return { - body: { - expiry: result.expiry - } + statusCode: HttpStatusCode.noContent }; } 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 8b41aa1..287ab25 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -1,6 +1,7 @@ // Copyright 2024 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. +import { randomUUID } from "node:crypto"; import type { IAuthenticationAdminComponent, IAuthenticationAuditComponent, @@ -71,6 +72,22 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA this._minPasswordLength = options?.config?.minPasswordLength; } + /** + * Map a stored account to the admin response. + * @param user The stored account. + * @returns The account fields returned to callers. The DID is included only when one is stored. + * @internal + */ + private static toAuthenticationUser(user: AuthenticationUser): IAuthenticationUser { + return { + email: user.email, + userId: user.userId, + ...(Is.stringValue(user.identity) ? { userIdentity: user.identity } : {}), + organizationIdentity: user.organization, + scope: user.scope.split(",") + }; + } + /** * Returns the class name of the component. * @returns The class name of the component. @@ -81,7 +98,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA /** * Create a login for the user. - * A missing identity is stored as the lowercased e-mail. A missing organisation is taken from the request context. + * The service allocates the account id. A DID is stored only when one is provided. A missing organisation is taken from the request context. * @param user The user to create. * @returns A promise that resolves when the user account has been created and the audit entry recorded. */ @@ -107,12 +124,15 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA user.scope ); - const userIdentity = user.userIdentity ?? user.email.trim().toLowerCase(); - Guards.stringValue( - EntityStorageAuthenticationAdminService.CLASS_NAME, - nameof(user.userIdentity), - userIdentity.trim() - ); + let userIdentity: string | undefined; + if (user.userIdentity !== undefined) { + userIdentity = user.userIdentity.trim(); + Guards.stringValue( + EntityStorageAuthenticationAdminService.CLASS_NAME, + nameof(user.userIdentity), + userIdentity + ); + } const contextIds = await ContextIdStore.getContextIds(); const organizationIdentity = @@ -123,6 +143,8 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA organizationIdentity.trim() ); + const userId = randomUUID(); + try { PasswordValidator.validatePassword(user.password, { minLength: this._minPasswordLength @@ -133,9 +155,14 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "userExists"); } - const existingIdentity = await this._userEntityStorage.get(userIdentity, "identity"); - if (Is.object(existingIdentity)) { - throw new GeneralError(EntityStorageAuthenticationAdminService.CLASS_NAME, "identityInUse"); + if (userIdentity !== undefined) { + const existingIdentity = await this._userEntityStorage.get(userIdentity, "identity"); + if (Is.object(existingIdentity)) { + throw new GeneralError( + EntityStorageAuthenticationAdminService.CLASS_NAME, + "identityInUse" + ); + } } const saltBytes = RandomHelper.generate(16); @@ -147,11 +174,14 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA email: user.email, salt: Converter.bytesToBase64(saltBytes), password: hashedPassword, - identity: userIdentity, + userId, organization: organizationIdentity, scope: user.scope.map(s => s.trim().toLocaleLowerCase()).join(","), passwordVersion: 0 }; + if (userIdentity !== undefined) { + newUser.identity = userIdentity; + } await this._userEntityStorage.set(newUser); @@ -160,7 +190,8 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA actorId: user.email, event: AuthAuditEvent.AccountCreated, data: { - userIdentity, + userId, + ...(userIdentity !== undefined ? { userIdentity } : {}), organizationIdentity, tenantId: requestorTenantId, scope: user.scope @@ -256,7 +287,8 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA event: AuthAuditEvent.AccountUpdated, data: { updatedFields, - userIdentity: existingUser.identity, + userId: existingUser.userId, + ...(Is.stringValue(existingUser.identity) ? { userIdentity: existingUser.identity } : {}), organizationIdentity: existingUser.organization, tenantId: requestorTenantId, scope: existingUser.scope.split(",") @@ -290,12 +322,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } - return { - email: user.email, - userIdentity: user.identity, - organizationIdentity: user.organization, - scope: user.scope.split(",") - }; + return EntityStorageAuthenticationAdminService.toAuthenticationUser(user); } catch (error) { throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, @@ -328,12 +355,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA ); } - return { - email: user.email, - userIdentity: user.identity, - organizationIdentity: user.organization, - scope: user.scope.split(",") - }; + return EntityStorageAuthenticationAdminService.toAuthenticationUser(user); } catch (error) { throw new GeneralError( EntityStorageAuthenticationAdminService.CLASS_NAME, @@ -370,7 +392,8 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA actorId: email, event: AuthAuditEvent.AccountDeleted, data: { - userIdentity: user.identity, + userId: user.userId, + ...(Is.stringValue(user.identity) ? { userIdentity: user.identity } : {}), organizationIdentity: user.organization, tenantId: requestorTenantId, scope: user.scope.split(",") 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 9fc0e87..bee0623 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -342,7 +342,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon this._vaultConnector, this._nodeId, this._signingKeyName, - user.identity, + user.userId, user.organization, loginTenantId, this._defaultTtlMinutes, @@ -370,7 +370,8 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon actorId: email, event: AuthAuditEvent.LoginSuccess, data: { - userIdentity: loginUser.identity, + userId: loginUser.userId, + ...(Is.stringValue(loginUser.identity) ? { userIdentity: loginUser.identity } : {}), organizationIdentity: loginUser.organization, tenantId: loginTenantId, scope: loginUser.scope.split(",") @@ -381,16 +382,13 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon } /** - * Replace an e-mail subject with a DID and issue a new token. - * @returns The new token and its expiry. + * Attach a DID to the signed-in account. The token subject stays the account id. + * @returns A promise that resolves when the DID has been stored. * @throws NotFoundError When the signed-in user cannot be loaded. * @throws ConflictError When the user already has a DID. * @throws GeneralError When the node or the identity component is not available, or the new document id is not a DID. */ - public async adoptIdentity(): Promise<{ - token?: string; - expiry: number; - }> { + public async adoptIdentity(): Promise { if (!Is.stringValue(this._nodeId)) { throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "nodeIdNotSet"); } @@ -399,8 +397,8 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon ContextIdHelper.guard(contextIds, ContextIdKeys.User); const subject = contextIds[ContextIdKeys.User]; - const user = await this._userEntityStorage.get(subject, "identity"); - if (!Is.object(user) || user.identity !== subject) { + const user = await this._userEntityStorage.get(subject, "userId"); + if (!Is.object(user) || user.userId !== subject) { throw new NotFoundError( EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", @@ -408,7 +406,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon ); } - if (user.identity.startsWith("did:")) { + if (user.identity?.startsWith("did:")) { throw new ConflictError( EntityStorageAuthenticationService.CLASS_NAME, "identityAlreadyAssigned" @@ -442,26 +440,14 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon } await this._authenticationAuditService?.create({ - actorId: documentId, + actorId: user.userId, event: AuthAuditEvent.AccountUpdated, data: { - previousIdentity: subject, + ...(Is.stringValue(user.identity) ? { previousIdentity: user.identity } : {}), userIdentity: documentId, organizationIdentity: user.organization } }); - - return TokenHelper.createToken( - this._vaultConnector, - this._nodeId, - this._signingKeyName, - documentId, - user.organization, - contextIds?.[ContextIdKeys.Tenant], - this._defaultTtlMinutes, - user.scope, - user.passwordVersion ?? 0 - ); } /** @@ -526,11 +512,11 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon // Wrap the user lookup in the request context so partitioned storage uses the correct tenant. const user = await ContextIdStore.run(contextIdsForUserLookup, async () => - this._userEntityStorage.get(sub, "identity") + this._userEntityStorage.get(sub, "userId") ); refreshPasswordVersion = user?.passwordVersion; - if (user?.identity === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) { + if (user?.userId === sub && (passwordVersion ?? 0) === (refreshPasswordVersion ?? 0)) { validParts.push("user"); } if (user?.organization === org) { @@ -582,15 +568,15 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon const contextIds = await ContextIdStore.getContextIds(); ContextIdHelper.guard(contextIds, ContextIdKeys.User); - const userIdentity = contextIds[ContextIdKeys.User]; - await this._authenticationRateService.check("password-change", userIdentity); + const userId = contextIds[ContextIdKeys.User]; + await this._authenticationRateService.check("password-change", userId); - const user = await this._userEntityStorage.get(userIdentity, "identity"); - if (!Is.object(user)) { + const user = await this._userEntityStorage.get(userId, "userId"); + if (!Is.object(user) || user.userId !== userId) { throw new NotFoundError( EntityStorageAuthenticationService.CLASS_NAME, "userNotFound", - userIdentity + userId ); } @@ -603,6 +589,6 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon this._minPasswordLength ); - await this._authenticationRateService.clear("password-change", userIdentity); + await this._authenticationRateService.clear("password-change", userId); } } 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..4cc4193 100644 --- a/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts +++ b/packages/api-auth-entity-storage-service/src/utils/passwordHelper.ts @@ -1,4 +1,5 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // 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"; @@ -57,18 +58,22 @@ export class PasswordHelper { email: user.email, salt: Converter.bytesToBase64(saltBytes), password: hashedPassword, - identity: user.identity, + userId: user.userId, organization: user.organization, scope: user.scope, passwordVersion: (user.passwordVersion ?? 0) + 1 }; + if (user.identity !== undefined) { + updatedUser.identity = user.identity; + } await userEntityStorage.set(updatedUser); await authenticationAuditService?.create({ actorId: user.email, event: AuthAuditEvent.PasswordChanged, data: { - userIdentity: updatedUser.identity, + userId: updatedUser.userId, + ...(Is.stringValue(updatedUser.identity) ? { userIdentity: updatedUser.identity } : {}), organizationIdentity: updatedUser.organization } }); 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..acaab8f 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 @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. import { HttpErrorHelper, type IHttpResponse } from "@3sixty/api-models"; import { ContextIdKeys, ContextIdStore, type IContextIds } from "@3sixty/context"; @@ -53,6 +54,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -67,11 +69,11 @@ describe("AuthHeaderProcessor", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { // No tid - single-tenant or tenant-free token; only user+org verified. - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); expect(verified).toEqual(["user", "organization"]); return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456" } + payload: { sub: "user-1", org: "did:org:456" } }; } ); @@ -83,10 +85,10 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.User]).toBe("user-1"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); }); it("should set tenant context from token when tenantAdminComponent is configured", async () => { @@ -107,6 +109,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -121,11 +124,11 @@ describe("AuthHeaderProcessor", () => { const tenantId = "tenant-1"; vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", tenantId, 0); + const verified = await verifyUser?.("user-1", "did:org:456", tenantId, 0); expect(verified).toContain("tenant"); return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: tenantId } + payload: { sub: "user-1", org: "did:org:456", tid: tenantId } }; } ); @@ -138,7 +141,7 @@ describe("AuthHeaderProcessor", () => { expect(response.statusCode).toBeUndefined(); expect(contextIds[ContextIdKeys.Tenant]).toBe("tenant-1"); - expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.User]).toBe("user-1"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); }); @@ -153,7 +156,7 @@ describe("AuthHeaderProcessor", () => { }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - await verifyUser?.("did:user:123", "did:org:456", undefined, undefined); + await verifyUser?.("user-1", "did:org:456", undefined, undefined); throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified"); } ); @@ -206,6 +209,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -221,13 +225,13 @@ describe("AuthHeaderProcessor", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { // pver=0 in token but user has passwordVersion=1 - simulate stale token - const verified = await verifyUser?.("did:user:123", "did:org:456", encryptedTenantId, 0); + const verified = await verifyUser?.("user-1", "did:org:456", encryptedTenantId, 0); if (!verified?.includes("user")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified"); } return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: encryptedTenantId } + payload: { sub: "user-1", org: "did:org:456", tid: encryptedTenantId } }; } ); @@ -244,7 +248,7 @@ describe("AuthHeaderProcessor", () => { expect(buildResponseSpy).toHaveBeenCalled(); expect(response.statusCode).toBe(HttpStatusCode.unauthorized); expect(contextIds[ContextIdKeys.User]).toBeUndefined(); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); }); it("should succeed when token has no pver and user has no passwordVersion stored", async () => { @@ -254,6 +258,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -267,11 +272,11 @@ describe("AuthHeaderProcessor", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { // No tid - pver behaviour is the focus here. - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, undefined); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, undefined); expect(verified).toEqual(["user", "organization"]); return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456" } + payload: { sub: "user-1", org: "did:org:456" } }; } ); @@ -283,10 +288,10 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.User]).toBe("user-1"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); }); it("should return unauthorized when token has an encrypted tenant but tenantAdminComponent is not configured", async () => { @@ -295,6 +300,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -309,13 +315,13 @@ describe("AuthHeaderProcessor", () => { const encryptedTenantId = "encrypted:tenant-1"; vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", encryptedTenantId, 0); + const verified = await verifyUser?.("user-1", "did:org:456", encryptedTenantId, 0); if (!verified?.includes("tenant")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "tenantNotVerified"); } return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: encryptedTenantId } + payload: { sub: "user-1", org: "did:org:456", tid: encryptedTenantId } }; } ); @@ -350,6 +356,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -364,11 +371,11 @@ describe("AuthHeaderProcessor", () => { const tenantA = "tenant-a"; vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", tenantA, 0); + const verified = await verifyUser?.("user-1", "did:org:456", tenantA, 0); expect(verified).toContain("tenant"); return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: tenantA } + payload: { sub: "user-1", org: "did:org:456", tid: tenantA } }; } ); @@ -384,7 +391,7 @@ describe("AuthHeaderProcessor", () => { expect(response.statusCode).toBeUndefined(); expect(contextIds[ContextIdKeys.Tenant]).toBe("tenant-a"); - expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.User]).toBe("user-1"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); }); @@ -395,6 +402,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "hashed", @@ -409,11 +417,11 @@ describe("AuthHeaderProcessor", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { // No tid in token - single-tenant system, no tenant verification required. - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); expect(verified).toEqual(["user", "organization"]); return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456" } + payload: { sub: "user-1", org: "did:org:456" } }; } ); @@ -426,7 +434,7 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); + expect(contextIds[ContextIdKeys.User]).toBe("user-1"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.Tenant]).toBeUndefined(); @@ -448,6 +456,7 @@ describe("AuthHeaderProcessor", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: USER_IDENTITY, organization: USER_ORG, password: "hashed", @@ -464,7 +473,7 @@ describe("AuthHeaderProcessor", () => { it("sets Organization context to _nodeOrganizationId when no tenant ID is in the token", async () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - await verifyUser?.(USER_IDENTITY, USER_ORG, undefined, 0); + await verifyUser?.("user-1", USER_ORG, undefined, 0); return { header: { alg: "EdDSA" }, payload: {} }; } ); @@ -482,7 +491,7 @@ describe("AuthHeaderProcessor", () => { it("sets UserOrganization to the user entity's own organization on successful verify", async () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - await verifyUser?.(USER_IDENTITY, USER_ORG, undefined, 0); + await verifyUser?.("user-1", USER_ORG, undefined, 0); return { header: { alg: "EdDSA" }, payload: {} }; } ); @@ -513,7 +522,7 @@ describe("AuthHeaderProcessor", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vault, nodeId, key, token, verifyUser) => { - await verifyUser?.(USER_IDENTITY, USER_ORG, "tenant-1", 0); + await verifyUser?.("user-1", USER_ORG, "tenant-1", 0); return { header: { alg: "EdDSA" }, payload: {} }; } ); diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts index 3a5a12b..2b10167 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -21,13 +21,10 @@ describe("authenticationAdoptIdentity", () => { vi.spyOn(ComponentFactory, "getIfExists").mockReturnValue(undefined); }); - it("should leave the token out of the body and out of the cookie for a bearer caller", async () => { + it("should leave the session untouched for a bearer caller", async () => { const authentication = { className: () => "authentication", - adoptIdentity: vi.fn().mockResolvedValue({ - token: "new-jwt-token", - expiry: 1722514341067 - }) + adoptIdentity: vi.fn().mockResolvedValue(undefined) }; vi.spyOn(ComponentFactory, "get").mockReturnValue(authentication); const httpRequestContext = { @@ -38,9 +35,7 @@ describe("authenticationAdoptIdentity", () => { } as unknown as IHttpRequestContext; const result = await authenticationAdoptIdentity(httpRequestContext, "authentication"); - const response: IHttpResponse = { - body: result.body - }; + const response: IHttpResponse = {}; const processor = new AuthHeaderProcessor(); await processor.post( { headers: {} } as never, @@ -50,22 +45,16 @@ describe("authenticationAdoptIdentity", () => { httpRequestContext.processorState ); - expect(result.body).toEqual({ expiry: 1722514341067 }); - expect(result.body).not.toHaveProperty("token"); - expect(JSON.stringify(result.body)).not.toContain("new-jwt-token"); - expect(httpRequestContext.processorState.authOperation).toBe("login"); - expect(httpRequestContext.processorState.authToken).toBe("new-jwt-token"); - expect(httpRequestContext.processorState.authTokenLocation).toBe("authorization"); + expect(result.statusCode).toBe(204); + expect(httpRequestContext.processorState.authOperation).toBeUndefined(); + expect(httpRequestContext.processorState.authToken).toBeUndefined(); expect(response.headers?.[HeaderTypes.SetCookie]).toBeUndefined(); }); - it("should set the new token as a cookie when the caller did not use a bearer token", async () => { + it("should leave the session cookie untouched when the caller did not use a bearer token", async () => { const authentication = { className: () => "authentication", - adoptIdentity: vi.fn().mockResolvedValue({ - token: "new-jwt-token", - expiry: 1722514341067 - }) + adoptIdentity: vi.fn().mockResolvedValue(undefined) }; vi.spyOn(ComponentFactory, "get").mockReturnValue(authentication); const httpRequestContext = { @@ -74,9 +63,7 @@ describe("authenticationAdoptIdentity", () => { } as unknown as IHttpRequestContext; const result = await authenticationAdoptIdentity(httpRequestContext, "authentication"); - const response: IHttpResponse = { - body: result.body - }; + const response: IHttpResponse = {}; const processor = new AuthHeaderProcessor(); await processor.post( { headers: {} } as never, @@ -86,6 +73,7 @@ describe("authenticationAdoptIdentity", () => { httpRequestContext.processorState ); - expect(response.headers?.[HeaderTypes.SetCookie]).toContain("new-jwt-token"); + expect(result.statusCode).toBe(204); + expect(response.headers?.[HeaderTypes.SetCookie]).toBeUndefined(); }); }); 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 a408e4f..34889f4 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,12 @@ // Copyright 2026 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. +const randomUUID = vi.hoisted(() => vi.fn(() => "user-1")); +vi.mock("node:crypto", async importOriginal => { + const actual = (await importOriginal()) as { [key: string]: unknown }; + return { ...actual, randomUUID }; +}); + import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-entity-storage-models"; import { ContextIdKeys, ContextIdStore } from "@3sixty/context"; import { ComponentFactory, GeneralError, RandomHelper } from "@3sixty/core"; @@ -21,6 +27,8 @@ describe("EntityStorageAuthenticationAdminService", () => { beforeEach(() => { vi.restoreAllMocks(); + randomUUID.mockReset(); + randomUUID.mockReturnValue("user-1"); mockAuthenticationAuditService = { className: vi.fn().mockReturnValue("AuthenticationAuditService"), @@ -79,6 +87,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", salt: "AQIDBA==", password: "hashed-password", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write", @@ -88,13 +97,35 @@ describe("EntityStorageAuthenticationAdminService", () => { actorId: "user@example.com", event: "account-created", data: { + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", + tenantId: undefined, scope: [" Read ", "WRITE"] } }); }); + it("should return the generated account id when a user is loaded by DID", 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"] + }); + + const result = await service.getByIdentity("did:user:123"); + + expect(result.userId).toBe("user-1"); + expect(result.userId).not.toBe(result.userIdentity); + expect(result.userIdentity).toBe("did:user:123"); + }); + it("should store the email and the context organisation when identities are omitted", async () => { vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); vi.spyOn(RandomHelper, "generate").mockReturnValue(new Uint8Array([1, 2, 3, 4])); @@ -112,7 +143,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "User@Example.com", salt: "AQIDBA==", password: "hashed-password", - identity: "user@example.com", + userId: "user-1", organization: "did:org:node", scope: "", passwordVersion: 0 @@ -121,7 +152,7 @@ describe("EntityStorageAuthenticationAdminService", () => { actorId: "User@Example.com", event: "account-created", data: { - userIdentity: "user@example.com", + userId: "user-1", organizationIdentity: "did:org:node", tenantId: undefined, scope: [] @@ -129,10 +160,11 @@ describe("EntityStorageAuthenticationAdminService", () => { }); }); - it("should reject a second account that would share the derived identity", async () => { + it("should create two accounts without a DID and give each its own id", async () => { vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); vi.spyOn(RandomHelper, "generate").mockReturnValue(new Uint8Array([1, 2, 3, 4])); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("hashed-password"); + randomUUID.mockReturnValueOnce("user-1").mockReturnValueOnce("user-2"); await ContextIdStore.run({ [ContextIdKeys.Organization]: "did:org:node" }, async () => { await service.create({ @@ -140,19 +172,23 @@ describe("EntityStorageAuthenticationAdminService", () => { password: "correct-horse-battery", scope: [] }); - await expect( - service.create({ - email: "user@example.com", - password: "correct-horse-battery", - scope: [] - }) - ).rejects.toThrow(); + await service.create({ + email: "other@example.com", + password: "correct-horse-battery", + scope: [] + }); }); - expect(await userEntityStorage.get("user@example.com")).toBeUndefined(); expect(await userEntityStorage.get("User@Example.com")).toMatchObject({ - identity: "user@example.com" + userId: "user-1", + email: "User@Example.com" }); + expect(await userEntityStorage.get("other@example.com")).toMatchObject({ + userId: "user-2", + email: "other@example.com" + }); + expect((await userEntityStorage.get("User@Example.com"))?.identity).toBeUndefined(); + expect((await userEntityStorage.get("other@example.com"))?.identity).toBeUndefined(); }); it("should reject a blank identity and write no user", async () => { @@ -202,6 +238,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -244,6 +281,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -259,6 +297,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:999", scope: "admin,write" @@ -268,7 +307,9 @@ describe("EntityStorageAuthenticationAdminService", () => { event: "account-updated", data: { updatedFields: ["organizationIdentity", "scope"], + userId: "user-1", userIdentity: "did:user:123", + tenantId: undefined, organizationIdentity: "did:org:999", scope: ["admin", "write"] } @@ -290,6 +331,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -299,6 +341,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", scope: ["read", "write"] @@ -310,6 +353,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -319,6 +363,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", scope: ["read", "write"] @@ -338,6 +383,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -350,7 +396,9 @@ describe("EntityStorageAuthenticationAdminService", () => { actorId: "user@example.com", event: "account-deleted", data: { + userId: "user-1", userIdentity: "did:user:123", + tenantId: undefined, organizationIdentity: "did:org:456", scope: ["read"] } @@ -369,6 +417,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -386,6 +435,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", salt: "CQgHBg==", password: "new-password-hash", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write", @@ -395,6 +445,7 @@ describe("EntityStorageAuthenticationAdminService", () => { actorId: "user@example.com", event: "password-changed", data: { + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456" } @@ -406,6 +457,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -422,6 +474,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", salt: "BQYHCA==", password: "new-password-hash", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write", @@ -434,6 +487,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -507,6 +561,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", salt: "AQIDBA==", password: "hashed-password", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read", @@ -521,6 +576,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -530,6 +586,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", scope: ["read", "write"] @@ -543,6 +600,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -552,6 +610,7 @@ describe("EntityStorageAuthenticationAdminService", () => { expect(result).toEqual({ email: "user@example.com", + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", scope: ["read", "write"] @@ -565,6 +624,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -589,6 +649,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -610,6 +671,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read,write" @@ -667,6 +729,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -684,6 +747,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -701,6 +765,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -726,6 +791,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" @@ -749,6 +815,7 @@ describe("EntityStorageAuthenticationAdminService", () => { email: "user@example.com", password: "stored-password", salt: "AQIDBA==", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", scope: "read" 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 ce65a3f..4a975d1 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 @@ -97,6 +97,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -119,7 +120,7 @@ describe("EntityStorageAuthenticationService", () => { mockVaultConnector, "node-1", "auth-signing", - "did:user:123", + "user-1", "did:org:456", "tenant-1", 60, @@ -131,6 +132,7 @@ describe("EntityStorageAuthenticationService", () => { actorId: "user@example.com", event: "login-success", data: { + userId: "user-1", userIdentity: "did:user:123", organizationIdentity: "did:org:456", tenantId: "tenant-1", @@ -139,12 +141,48 @@ describe("EntityStorageAuthenticationService", () => { }); }); + it("should login with the account id when the account has no DID", async () => { + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1" + }); + vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ + email: "user@example.com", + userId: "user-1", + organization: "did:org:456", + password: "stored-password-hash", + salt: "c2FsdA==", + scope: "read" + }); + vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); + vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(true); + vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ + token: "jwt-token", + expiry: 123456789 + }); + + await service.start(); + await service.login("user@example.com", "correct-password"); + + expect(TokenHelper.createToken).toHaveBeenCalledWith( + mockVaultConnector, + "node-1", + "auth-signing", + "user-1", + "did:org:456", + undefined, + 60, + "read", + 0 + ); + }); + it("should throw UnauthorizedError when login password does not match", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -186,6 +224,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -272,6 +311,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -308,6 +348,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -316,12 +357,12 @@ describe("EntityStorageAuthenticationService", () => { }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); expect(verified).toEqual(["user", "organization"]); return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456", scope: "read,write" } @@ -337,25 +378,22 @@ describe("EntityStorageAuthenticationService", () => { const result = await service.refresh("existing-token"); expect(result).toEqual({ token: "refreshed-token", expiry: 987654321 }); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(TokenHelper.createToken).toHaveBeenCalledWith( mockVaultConnector, "node-1", "auth-signing", - "did:user:123", + "user-1", "did:org:456", undefined, 60, "read,write", 0 ); - expect(mockAuthenticationRateService.check).toHaveBeenCalledWith( - "token-refresh", - "did:user:123" - ); + expect(mockAuthenticationRateService.check).toHaveBeenCalledWith("token-refresh", "user-1"); expect(mockAuthenticationRateService.clear).not.toHaveBeenCalled(); expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ - actorId: "did:user:123", + actorId: "user-1", event: "token-refreshed", data: { organizationIdentity: "did:org:456", @@ -372,11 +410,11 @@ describe("EntityStorageAuthenticationService", () => { }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - await verifyUser?.("did:user:123", "did:org:456", undefined, undefined); + await verifyUser?.("user-1", "did:org:456", undefined, undefined); return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456", tid: "tenant-1", scope: "read,write" @@ -422,6 +460,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:other", organization: "did:org:456", password: "stored-password-hash", @@ -431,7 +470,7 @@ describe("EntityStorageAuthenticationService", () => { const createTokenSpy = vi.spyOn(TokenHelper, "createToken"); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-other", "did:org:456", undefined, 0); if (!verified?.includes("user")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified"); } @@ -439,7 +478,7 @@ describe("EntityStorageAuthenticationService", () => { return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-other", org: "did:org:456" } }; @@ -449,7 +488,7 @@ describe("EntityStorageAuthenticationService", () => { await service.start(); await expect(service.refresh("existing-token")).rejects.toThrow(UnauthorizedError); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-other", "userId"); expect(createTokenSpy).not.toHaveBeenCalled(); }); @@ -459,6 +498,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:other", password: "stored-password-hash", @@ -468,7 +508,7 @@ describe("EntityStorageAuthenticationService", () => { const createTokenSpy = vi.spyOn(TokenHelper, "createToken"); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); if (!verified?.includes("organization")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "organizationNotVerified"); } @@ -476,7 +516,7 @@ describe("EntityStorageAuthenticationService", () => { return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456" } }; @@ -486,7 +526,7 @@ describe("EntityStorageAuthenticationService", () => { await service.start(); await expect(service.refresh("existing-token")).rejects.toThrow(UnauthorizedError); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(createTokenSpy).not.toHaveBeenCalled(); }); @@ -496,6 +536,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -507,13 +548,13 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { // pver=1 in token but user has passwordVersion=2 - simulate stale token - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 1); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 1); if (!verified?.includes("user")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified"); } return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456" } + payload: { sub: "user-1", org: "did:org:456" } }; } ); @@ -521,16 +562,17 @@ describe("EntityStorageAuthenticationService", () => { await service.start(); await expect(service.refresh("existing-token")).rejects.toThrow(UnauthorizedError); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(createTokenSpy).not.toHaveBeenCalled(); }); it("should update the password for the current user", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "did:user:123" + [ContextIdKeys.User]: "user-1" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -541,11 +583,8 @@ describe("EntityStorageAuthenticationService", () => { await service.updatePassword("current-password", "new-password"); - expect(mockAuthenticationRateService.check).toHaveBeenCalledWith( - "password-change", - "did:user:123" - ); - expect(mockUserEntityStorage.get).toHaveBeenCalledWith("did:user:123", "identity"); + expect(mockAuthenticationRateService.check).toHaveBeenCalledWith("password-change", "user-1"); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(PasswordHelper.updatePassword).toHaveBeenCalledWith( mockUserEntityStorage, mockAuthenticationAuditService, @@ -554,15 +593,12 @@ describe("EntityStorageAuthenticationService", () => { "current-password", undefined ); - expect(mockAuthenticationRateService.clear).toHaveBeenCalledWith( - "password-change", - "did:user:123" - ); + expect(mockAuthenticationRateService.clear).toHaveBeenCalledWith("password-change", "user-1"); }); it("should throw TooManyRequestsError when password change rate limit is exceeded", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "did:user:123" + [ContextIdKeys.User]: "user-1" }); vi.mocked(mockAuthenticationRateService.check).mockRejectedValue( new TooManyRequestsError( @@ -594,12 +630,12 @@ describe("EntityStorageAuthenticationService", () => { it("should logout without throwing", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "did:user:123" + [ContextIdKeys.User]: "user-1" }); await expect(service.logout("token")).resolves.toBeUndefined(); expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ - actorId: "did:user:123", + actorId: "user-1", event: "logout" }); }); @@ -638,6 +674,7 @@ describe("EntityStorageAuthenticationService", () => { async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -655,6 +692,7 @@ describe("EntityStorageAuthenticationService", () => { await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_A }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -683,6 +721,7 @@ describe("EntityStorageAuthenticationService", () => { await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_A }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:tenant-a", organization: "did:org:456", password: "stored-password-hash", @@ -694,6 +733,7 @@ describe("EntityStorageAuthenticationService", () => { await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_B }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-b", identity: "did:user:tenant-b", organization: "did:org:456", password: "stored-password-hash", @@ -726,10 +766,11 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(PasswordHelper, "updatePassword").mockResolvedValue(undefined); await ContextIdStore.run( - { [ContextIdKeys.Tenant]: TENANT_A, [ContextIdKeys.User]: "did:user:123" }, + { [ContextIdKeys.Tenant]: TENANT_A, [ContextIdKeys.User]: "user-1" }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -755,6 +796,7 @@ describe("EntityStorageAuthenticationService", () => { await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_A }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -764,7 +806,7 @@ describe("EntityStorageAuthenticationService", () => { }); await ContextIdStore.run( - { [ContextIdKeys.Tenant]: TENANT_B, [ContextIdKeys.User]: "did:user:123" }, + { [ContextIdKeys.Tenant]: TENANT_B, [ContextIdKeys.User]: "user-1" }, async () => { await expect(service.updatePassword("current-password", "new-password")).rejects.toThrow( NotFoundError @@ -777,6 +819,7 @@ describe("EntityStorageAuthenticationService", () => { await ContextIdStore.run({ [ContextIdKeys.Tenant]: TENANT_A }, async () => { await userEntityStorage.set({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -787,13 +830,13 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", undefined, 0); + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); if (!verified?.includes("user")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "userNotVerified"); } return { header: { alg: "EdDSA" }, - payload: { sub: "did:user:123", org: "did:org:456", tid: TENANT_B, scope: "read" } + payload: { sub: "user-1", org: "did:org:456", tid: TENANT_B, scope: "read" } }; } ); @@ -813,6 +856,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -821,14 +865,14 @@ describe("EntityStorageAuthenticationService", () => { }); vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", "tenant-a", 0); + const verified = await verifyUser?.("user-1", "did:org:456", "tenant-a", 0); if (!verified?.includes("tenant")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "tenantNotVerified"); } return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456", tid: "tenant-a", scope: "read,write" @@ -848,6 +892,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -873,14 +918,14 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", "tenant-a", 0); + const verified = await verifyUser?.("user-1", "did:org:456", "tenant-a", 0); if (!verified?.includes("tenant")) { throw new UnauthorizedError(TokenHelper.CLASS_NAME, "tenantNotVerified"); } return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456", tid: "tenant-a", scope: "read,write" @@ -901,6 +946,7 @@ describe("EntityStorageAuthenticationService", () => { }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", + userId: "user-1", identity: "did:user:123", organization: "did:org:456", password: "stored-password-hash", @@ -925,12 +971,12 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(TokenHelper, "verify").mockImplementation( async (vaultConnector, nodeId, signingKeyName, token, verifyUser) => { - const verified = await verifyUser?.("did:user:123", "did:org:456", "tenant-a", 0); + const verified = await verifyUser?.("user-1", "did:org:456", "tenant-a", 0); expect(verified).toContain("tenant"); return { header: { alg: "EdDSA" }, payload: { - sub: "did:user:123", + sub: "user-1", org: "did:org:456", tid: "tenant-a", scope: "read,write" @@ -952,7 +998,7 @@ describe("EntityStorageAuthenticationService", () => { mockVaultConnector, "node-1", "auth-signing", - "did:user:123", + "user-1", "did:org:456", "tenant-a", 60, @@ -975,7 +1021,7 @@ describe("EntityStorageAuthenticationService", () => { }; user = { email: "User@Example.com", - identity: "user@example.com", + userId: "user-1", organization: "did:org:node", password: "stored-password-hash", salt: "c2FsdA==", @@ -995,7 +1041,7 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1", [ContextIdKeys.Tenant]: "tenant-1", - [ContextIdKeys.User]: "user@example.com" + [ContextIdKeys.User]: "user-1" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue(user); vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ @@ -1005,54 +1051,43 @@ describe("EntityStorageAuthenticationService", () => { await service.start(); }); - it("should replace an email subject with the new DID and issue a token for it", async () => { + it("should store the DID on the account and leave the token subject unchanged", async () => { const result = await service.adoptIdentity(); expect(vi.mocked(identity.identityCreateSelf)).toHaveBeenCalledTimes(1); + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(mockUserEntityStorage.set).toHaveBeenCalledWith( expect.objectContaining({ email: "User@Example.com", + userId: "user-1", identity: documentId, organization: "did:org:node", scope: "", passwordVersion: 0 }) ); - expect(user.identity).toBe("user@example.com"); - expect(TokenHelper.createToken).toHaveBeenCalledWith( - mockVaultConnector, - "node-1", - "auth-signing", - documentId, - "did:org:node", - "tenant-1", - 60, - "", - 0 - ); - expect(result).toEqual({ token: "new-jwt-token", expiry: 1722514341067 }); - expect(mockAuthenticationRateService.check).toHaveBeenCalledWith( - "identity-adopt", - "user@example.com" - ); + expect(user.identity).toBeUndefined(); + expect(TokenHelper.createToken).not.toHaveBeenCalled(); + expect(result).toBeUndefined(); + expect(mockAuthenticationRateService.check).toHaveBeenCalledWith("identity-adopt", "user-1"); expect(mockAuthenticationAuditService.create).toHaveBeenCalledWith({ - actorId: documentId, + actorId: "user-1", event: "account-updated", data: { - previousIdentity: "user@example.com", userIdentity: documentId, organizationIdentity: "did:org:node" } }); - expect(JSON.stringify(result)).not.toContain(mnemonic); - expect(JSON.stringify(vi.mocked(TokenHelper.createToken).mock.calls)).not.toContain(mnemonic); + expect( + JSON.stringify(vi.mocked(mockAuthenticationAuditService.create).mock.calls) + ).not.toContain(mnemonic); }); it("should reject when the user already has a DID and leave the row unchanged", async () => { user.identity = "did:iota:tst:existing"; vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1", - [ContextIdKeys.User]: "did:iota:tst:existing" + [ContextIdKeys.User]: "user-1" }); await expect(service.adoptIdentity()).rejects.toBeInstanceOf(ConflictError); @@ -1067,7 +1102,7 @@ describe("EntityStorageAuthenticationService", () => { await expect(service.adoptIdentity()).rejects.toThrow("publish failed"); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); expect(TokenHelper.createToken).not.toHaveBeenCalled(); - expect(user.identity).toBe("user@example.com"); + expect(user.identity).toBeUndefined(); expect(JSON.stringify(vi.mocked(identity.identityCreateSelf).mock.results)).not.toContain( "token" ); @@ -1083,7 +1118,7 @@ describe("EntityStorageAuthenticationService", () => { documentId ); expect(TokenHelper.createToken).not.toHaveBeenCalled(); - expect(user.identity).toBe("user@example.com"); + expect(user.identity).toBeUndefined(); }); it("should keep a recorded mnemonic out of the failure", async () => { @@ -1098,5 +1133,43 @@ describe("EntityStorageAuthenticationService", () => { expect(rejection).not.toContain(mnemonic); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); }); + + it("should refresh with the same account id after a DID is stored", async () => { + user.identity = documentId; + vi.spyOn(TokenHelper, "verify").mockImplementation( + async (vault, nodeId, signingKeyName, token, verifyUser) => { + const verified = await verifyUser?.("user-1", "did:org:node", "tenant-1", 0); + expect(verified).toContain("user"); + return { + header: { alg: "EdDSA" }, + payload: { + sub: "user-1", + org: "did:org:node", + tid: "tenant-1", + scope: "" + } + }; + } + ); + vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ + token: "refreshed-token", + expiry: 987654321 + }); + + await service.refresh("existing-token"); + + expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); + expect(TokenHelper.createToken).toHaveBeenCalledWith( + mockVaultConnector, + "node-1", + "auth-signing", + "user-1", + "did:org:node", + "tenant-1", + 60, + "", + 0 + ); + }); }); }); diff --git a/packages/api-auth-entity-storage-service/tests/tsconfig.json b/packages/api-auth-entity-storage-service/tests/tsconfig.json index 44d8c8f..6e14e0f 100644 --- a/packages/api-auth-entity-storage-service/tests/tsconfig.json +++ b/packages/api-auth-entity-storage-service/tests/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "rootDir": "..", - "types": ["vitest/globals"] + "types": ["vitest/globals", "node"] }, "include": ["./**/*.ts"] } From f6368207dc1f7658e4ad6111c9f89493a6213163 Mon Sep 17 00:00:00 2001 From: Adrian Sanchez Date: Fri, 23 Oct 2026 10:23:05 +0200 Subject: [PATCH 6/8] fix: pr reviews #5 --- .../src/entities/authenticationUser.ts | 2 +- .../src/index.ts | 2 + .../src/schema.ts | 19 ++++++++ ...entityStorageAuthenticationAdminService.ts | 3 +- ...yStorageAuthenticationAdminService.spec.ts | 13 ++---- ...entityStorageAuthenticationService.spec.ts | 44 +++++++++++++++++++ .../tests/tsconfig.json | 2 +- 7 files changed, 72 insertions(+), 13 deletions(-) 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 3409d9d..b50f20c 100644 --- a/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts +++ b/packages/api-auth-entity-storage-service/src/entities/authenticationUser.ts @@ -6,7 +6,7 @@ import { entity, property } from "@3sixty/entity"; /** * Class defining the storage for user login credentials. */ -@entity({ version: 1 }) +@entity({ version: 2 }) export class AuthenticationUser { /** * The user e-mail address. diff --git a/packages/api-auth-entity-storage-service/src/index.ts b/packages/api-auth-entity-storage-service/src/index.ts index 10132b6..b9cc9fc 100644 --- a/packages/api-auth-entity-storage-service/src/index.ts +++ b/packages/api-auth-entity-storage-service/src/index.ts @@ -1,7 +1,9 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. export * from "./entities/authenticationUser.js"; export * from "./entities/authenticationUserV0.js"; +export * from "./entities/authenticationUserV1.js"; export * from "./models/IAuthHeaderProcessorConfig.js"; export * from "./models/IAuthHeaderProcessorConstructorOptions.js"; export * from "./models/IAuthTokenContext.js"; diff --git a/packages/api-auth-entity-storage-service/src/schema.ts b/packages/api-auth-entity-storage-service/src/schema.ts index 3fe63a2..972e565 100644 --- a/packages/api-auth-entity-storage-service/src/schema.ts +++ b/packages/api-auth-entity-storage-service/src/schema.ts @@ -1,9 +1,13 @@ // Copyright 2024 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. +import { RandomHelper } from "@3sixty/core"; import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity"; +import { SchemaMigrationFactory, type ISchemaMigration } from "@3sixty/entity-storage-models"; import { nameof } from "@3sixty/nameof"; import { AuthenticationUser } from "./entities/authenticationUser.js"; import { AuthenticationUserV0 } from "./entities/authenticationUserV0.js"; +import { AuthenticationUserV1 } from "./entities/authenticationUserV1.js"; /** * Initialize the schema for the authentication service. @@ -15,4 +19,19 @@ export function initSchema(): void { EntitySchemaFactory.register(nameof(), () => EntitySchemaHelper.getSchema(AuthenticationUserV0) ); + EntitySchemaFactory.register(nameof(), () => + EntitySchemaHelper.getSchema(AuthenticationUserV1) + ); + + // A stored account has an identity and no account id. The id is new, so the row cannot supply it. + const allocateAccountId: ISchemaMigration = { + transformEntity: entity => ({ + ...entity, + userId: RandomHelper.generateUuidV7() + }) + }; + SchemaMigrationFactory.register( + `${nameof()}_1_2`, + () => allocateAccountId as ISchemaMigration + ); } 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 2774c84..1ea423f 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationAdminService.ts @@ -1,7 +1,6 @@ // Copyright 2024 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -import { randomUUID } from "node:crypto"; import type { IAuthenticationAdminComponent, IAuthenticationUser, @@ -184,7 +183,7 @@ export class EntityStorageAuthenticationAdminService implements IAuthenticationA organizationIdentity.trim() ); - const userId = randomUUID(); + const userId = RandomHelper.generateUuidV7(); try { PasswordValidator.validatePassword(user.password, { 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 420d59d..c11c984 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,12 +1,6 @@ // Copyright 2026 IOTA Stiftung. // Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. -const randomUUID = vi.hoisted(() => vi.fn(() => "user-1")); -vi.mock("node:crypto", async importOriginal => { - const actual = (await importOriginal()) as { [key: string]: unknown }; - return { ...actual, randomUUID }; -}); - import type { IAuthenticationAuditComponent } from "@3sixty/api-auth-models"; import { ForbiddenError } from "@3sixty/api-models"; import type { IAuthorizationComponent } from "@3sixty/authorization-models"; @@ -29,8 +23,7 @@ describe("EntityStorageAuthenticationAdminService", () => { beforeEach(() => { vi.restoreAllMocks(); - randomUUID.mockReset(); - randomUUID.mockReturnValue("user-1"); + vi.spyOn(RandomHelper, "generateUuidV7").mockReturnValue("user-1"); mockAuthenticationAuditService = { className: vi.fn().mockReturnValue("AuthenticationAuditService"), @@ -162,7 +155,9 @@ describe("EntityStorageAuthenticationAdminService", () => { vi.spyOn(PasswordValidator, "validatePassword").mockImplementation(() => {}); vi.spyOn(RandomHelper, "generate").mockReturnValue(new Uint8Array([1, 2, 3, 4])); vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("hashed-password"); - randomUUID.mockReturnValueOnce("user-1").mockReturnValueOnce("user-2"); + vi.spyOn(RandomHelper, "generateUuidV7") + .mockReturnValueOnce("user-1") + .mockReturnValueOnce("user-2"); await ContextIdStore.run({ [ContextIdKeys.Organization]: "did:org:node" }, async () => { await service.create({ 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 3867d9f..92d867c 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 @@ -22,6 +22,7 @@ import { initSchema } from "../../src/schema.js"; import { EntityStorageAuthenticationService } from "../../src/services/entityStorageAuthenticationService.js"; import { PasswordHelper } from "../../src/utils/passwordHelper.js"; import { TokenHelper } from "../../src/utils/tokenHelper.js"; +import { migrateAuthenticationUser } from "../helpers/migrateAuthenticationUser.js"; initSchema(); @@ -138,6 +139,49 @@ describe("EntityStorageAuthenticationService", () => { }); }); + it("should login a stored account with the id allocated by migration", async () => { + const migrated = await migrateAuthenticationUser( + { + email: "user@example.com", + password: "stored-password-hash", + salt: "c2FsdA==", + identity: "did:user:123", + organization: "did:org:456", + scope: "read" + }, + 0 + ); + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1", + [ContextIdKeys.Tenant]: "tenant-1" + }); + vi.mocked(mockUserEntityStorage.get).mockResolvedValue(migrated); + vi.spyOn(PasswordGenerator, "hashPassword").mockResolvedValue("generated-password-hash"); + vi.spyOn(PasswordValidator, "comparePasswordHashes").mockReturnValue(true); + vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ + token: "jwt-token", + expiry: 123456789 + }); + + await service.start(); + await service.login("user@example.com", "correct-password"); + + expect(migrated.userId).toEqual(expect.any(String)); + expect(migrated.userId).not.toBe("did:user:123"); + expect(migrated.identity).toBe("did:user:123"); + expect(migrated).not.toHaveProperty("scope"); + expect(TokenHelper.createToken).toHaveBeenCalledWith( + mockVaultConnector, + "node-1", + "auth-signing", + migrated.userId, + "did:org:456", + "tenant-1", + 60, + 0 + ); + }); + it("should login with the account id when the account has no DID", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1" diff --git a/packages/api-auth-entity-storage-service/tests/tsconfig.json b/packages/api-auth-entity-storage-service/tests/tsconfig.json index abcb216..8f550b2 100644 --- a/packages/api-auth-entity-storage-service/tests/tsconfig.json +++ b/packages/api-auth-entity-storage-service/tests/tsconfig.json @@ -4,7 +4,7 @@ "noEmit": true, "tsBuildInfoFile": "../.cache/tests.tsbuildinfo", "rootDir": "..", - "types": ["vitest/globals", "node"] + "types": ["vitest/globals"] }, "include": ["./**/*.ts"] } From 6f0d640193272510819831f88f09cbee267d0d70 Mon Sep 17 00:00:00 2001 From: Adrian Sanchez Date: Fri, 23 Oct 2026 10:28:18 +0200 Subject: [PATCH 7/8] fix: include the account schema migration files --- .../src/entities/authenticationUserV1.ts | 46 ++++++++++++++++++ .../helpers/migrateAuthenticationUser.ts | 48 +++++++++++++++++++ .../tests/schema.spec.ts | 41 ++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 packages/api-auth-entity-storage-service/src/entities/authenticationUserV1.ts create mode 100644 packages/api-auth-entity-storage-service/tests/helpers/migrateAuthenticationUser.ts create mode 100644 packages/api-auth-entity-storage-service/tests/schema.spec.ts diff --git a/packages/api-auth-entity-storage-service/src/entities/authenticationUserV1.ts b/packages/api-auth-entity-storage-service/src/entities/authenticationUserV1.ts new file mode 100644 index 0000000..0f2191f --- /dev/null +++ b/packages/api-auth-entity-storage-service/src/entities/authenticationUserV1.ts @@ -0,0 +1,46 @@ +// Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. +import { entity, property } from "@3sixty/entity"; + +/** + * Account storage after authorization removed the stored scope, and before each account had its own id. + */ +@entity({ version: 1 }) +export class AuthenticationUserV1 { + /** + * The user e-mail address. + */ + @property({ type: "string", format: "email", 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", maxLength: 255, isSecondary: true }) + public identity!: string; + + /** + * The users organization. + */ + @property({ type: "string", maxLength: 255 }) + public organization!: 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/tests/helpers/migrateAuthenticationUser.ts b/packages/api-auth-entity-storage-service/tests/helpers/migrateAuthenticationUser.ts new file mode 100644 index 0000000..ee237fc --- /dev/null +++ b/packages/api-auth-entity-storage-service/tests/helpers/migrateAuthenticationUser.ts @@ -0,0 +1,48 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. +import { EntitySchemaFactory, EntitySchemaHelper } from "@3sixty/entity"; +import { + MigrationHelper, + SchemaMigrationFactory, + type IResolvedMigrationStep +} from "@3sixty/entity-storage-models"; +import { nameof } from "@3sixty/nameof"; +import type { AuthenticationUser } from "../../src/entities/authenticationUser.js"; + +/** + * Run the stored account forward to the current schema, the same way the node does at start-up. + * @param entity The account as it was stored. + * @param fromVersion The version it was stored at. + * @returns The account at the current version. + */ +export async function migrateAuthenticationUser( + entity: unknown, + fromVersion: number +): Promise { + const currentVersion = EntitySchemaHelper.getVersion( + EntitySchemaFactory.get(nameof()) + ); + const steps: IResolvedMigrationStep[] = []; + + for (let version = fromVersion; version < currentVersion; version++) { + const fromSchema = EntitySchemaFactory.get(`${nameof()}V${version}`); + const toSchema = + version + 1 < currentVersion + ? EntitySchemaFactory.get(`${nameof()}V${version + 1}`) + : EntitySchemaFactory.get(nameof()); + const override = SchemaMigrationFactory.getIfExists( + `${nameof()}_${version}_${version + 1}` + ); + + steps.push({ + fromProperties: fromSchema.properties ?? [], + toProperties: toSchema.properties ?? [], + renames: override?.renames, + transformEntity: override?.transformEntity, + transformEntityProperty: override?.transformEntityProperty, + removeEntityProperty: override?.removeEntityProperty + }); + } + + return (await MigrationHelper.applyEntityChain(entity, steps)) as AuthenticationUser; +} diff --git a/packages/api-auth-entity-storage-service/tests/schema.spec.ts b/packages/api-auth-entity-storage-service/tests/schema.spec.ts new file mode 100644 index 0000000..7fcbeb9 --- /dev/null +++ b/packages/api-auth-entity-storage-service/tests/schema.spec.ts @@ -0,0 +1,41 @@ +// Copyright 2026 3Sixty Trade Ltd. +// SPDX-License-Identifier: Apache-2.0. +import { RandomHelper } from "@3sixty/core"; +import { initSchema } from "../src/schema.js"; +import { migrateAuthenticationUser } from "./helpers/migrateAuthenticationUser.js"; + +initSchema(); + +describe("AuthenticationUser migration", () => { + const storedAccount = { + email: "user@example.com", + password: "stored-password-hash", + salt: "c2FsdA==", + identity: "did:user:123", + organization: "did:org:456", + passwordVersion: 2 + }; + + beforeEach(() => { + vi.restoreAllMocks(); + vi.spyOn(RandomHelper, "generateUuidV7").mockReturnValue("account-1"); + }); + + test("gives a version 1 account its own id and keeps the identity", async () => { + const migrated = await migrateAuthenticationUser(storedAccount, 1); + + expect(migrated.userId).toBe("account-1"); + expect(migrated.identity).toBe("did:user:123"); + expect(migrated.email).toBe("user@example.com"); + expect(migrated.organization).toBe("did:org:456"); + expect(migrated.passwordVersion).toBe(2); + }); + + test("drops the stored scope from a version 0 account and keeps the identity", async () => { + const migrated = await migrateAuthenticationUser({ ...storedAccount, scope: "read" }, 0); + + expect(migrated.userId).toBe("account-1"); + expect(migrated.identity).toBe("did:user:123"); + expect(migrated).not.toHaveProperty("scope"); + }); +}); From fc4654c4b61eafb9c74bda931dad06009b6ebdd6 Mon Sep 17 00:00:00 2001 From: Adrian Sanchez Date: Mon, 26 Oct 2026 09:14:19 +0100 Subject: [PATCH 8/8] feat: adopt a self-controlled identity for an account --- .../package.json | 1 + .../src/models/IAuthTokenContext.ts | 6 + .../src/models/IIdentityAdoption.ts | 35 -- .../src/processors/authHeaderProcessor.ts | 12 +- .../entityStorageAuthenticationRoutes.ts | 6 +- .../entityStorageAuthenticationService.ts | 28 +- .../processors/authHeaderProcessor.spec.ts | 62 ++- .../entityStorageAuthenticationRoutes.spec.ts | 20 +- ...entityStorageAuthenticationService.spec.ts | 39 +- pnpm-lock.yaml | 392 +++++++++++++++++- 10 files changed, 506 insertions(+), 95 deletions(-) delete mode 100644 packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts diff --git a/packages/api-auth-entity-storage-service/package.json b/packages/api-auth-entity-storage-service/package.json index aa37992..927191a 100644 --- a/packages/api-auth-entity-storage-service/package.json +++ b/packages/api-auth-entity-storage-service/package.json @@ -38,6 +38,7 @@ "@3sixty/crypto": "next", "@3sixty/entity": "next", "@3sixty/entity-storage-models": "next", + "@3sixty/identity-models": "next", "@3sixty/nameof": "next", "@3sixty/vault-models": "next", "@3sixty/web": "next" diff --git a/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts b/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts index a59abbc..97fe913 100644 --- a/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts +++ b/packages/api-auth-entity-storage-service/src/models/IAuthTokenContext.ts @@ -1,4 +1,5 @@ // Copyright 2026 IOTA Stiftung. +// Copyright 2026 3Sixty Trade Ltd. // SPDX-License-Identifier: Apache-2.0. /** @@ -26,6 +27,11 @@ export interface IAuthTokenContext { */ userIdentity?: string; + /** + * The stored identity of the verified account, when it has one. + */ + identity?: string; + /** * The organization of the verified user. */ diff --git a/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts b/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts deleted file mode 100644 index 37c9860..0000000 --- a/packages/api-auth-entity-storage-service/src/models/IIdentityAdoption.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2026 3Sixty Trade Ltd. -// SPDX-License-Identifier: Apache-2.0. -import type { IComponent } from "@3sixty/core"; - -/** - * The identity operations this service needs in order to attach a DID. - * The identity package owns the full component. This is the slice used here. - */ -export interface IIdentityAdoption extends IComponent { - /** - * Create a document controlled by the new DID. - * @returns The new document. Only the id is used. - */ - identityCreateSelf(): Promise<{ - /** - * The DID of the new document. - */ - id: string; - }>; - - /** - * Remove a document. - * @param identity The document id to remove. - * @param options Optional removal settings. - * @param options.removeKeys Also remove associated private keys from the vault. - * @param options.removeDocumentKey Also remove the document key from the vault. - * @param controller The controller allowed to remove the document. - * @returns A promise that resolves when the document has been removed. - */ - identityRemove( - identity: string, - options?: { removeKeys?: boolean; removeDocumentKey?: boolean }, - controller?: string - ): Promise; -} 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 2bdf112..f2388db 100644 --- a/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts +++ b/packages/api-auth-entity-storage-service/src/processors/authHeaderProcessor.ts @@ -208,7 +208,16 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { // in a single-tenant environment we fall back to the node organization ID. contextIds[ContextIdKeys.Organization] = tokenContext.tenantOrganizationId ?? this._nodeOrganizationId; - contextIds[ContextIdKeys.User] = tokenContext.userIdentity; + if (Is.stringValue(tokenContext.userIdentity)) { + contextIds[ContextIdKeys.Account] = tokenContext.userIdentity; + } else { + delete contextIds[ContextIdKeys.Account]; + } + if (Is.stringValue(tokenContext.identity) && tokenContext.identity.startsWith("did:")) { + contextIds[ContextIdKeys.User] = tokenContext.identity; + } else { + delete contextIds[ContextIdKeys.User]; + } contextIds[ContextIdKeys.UserOrganization] = tokenContext.userOrganization; // If the tenant has a custom public origin, we set it in the context for downstream processors to use. @@ -389,6 +398,7 @@ export class AuthHeaderProcessor implements IBaseRouteProcessor { tenantOrganizationId, tenantPublicOrigin, userIdentity: user?.userId, + identity: Is.stringValue(user?.identity) ? user.identity : undefined, userOrganization: user?.organization, expires: Is.integer(expiresSeconds) ? expiresSeconds * 1000 : undefined }; 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 530cff9..c9c9555 100644 --- a/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts +++ b/packages/api-auth-entity-storage-service/src/routes/entityStorageAuthenticationRoutes.ts @@ -197,7 +197,8 @@ export function generateRestRoutesAuthentication( { type: nameof() } - ] + ], + requiresAuthorization: false }; const adoptIdentityRoute: IRestRoute = { @@ -218,7 +219,8 @@ export function generateRestRoutesAuthentication( { type: nameof() } - ] + ], + requiresAuthorization: false }; const passwordPolicyRoute: IRestRoute = { 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 e2cd28f..78e3e90 100644 --- a/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts +++ b/packages/api-auth-entity-storage-service/src/services/entityStorageAuthenticationService.ts @@ -29,11 +29,11 @@ import { EntityStorageConnectorFactory, type IEntityStorageConnector } from "@3sixty/entity-storage-models"; +import type { IIdentityComponent } from "@3sixty/identity-models"; import { nameof } from "@3sixty/nameof"; import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import type { AuthenticationUser } from "../entities/authenticationUser.js"; import type { IEntityStorageAuthenticationServiceConstructorOptions } from "../models/IEntityStorageAuthenticationServiceConstructorOptions.js"; -import type { IIdentityAdoption } from "../models/IIdentityAdoption.js"; import { PasswordHelper } from "../utils/passwordHelper.js"; import { TokenHelper } from "../utils/tokenHelper.js"; @@ -182,7 +182,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon * The identity component used to create a self-controlled DID. * @internal */ - private readonly _identityComponent?: IIdentityAdoption; + private readonly _identityComponent?: IIdentityComponent; /** * The node identity. @@ -213,7 +213,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon options?.tenantAdminComponentType ?? "tenant-admin" ); - this._identityComponent = ComponentFactory.getIfExists( + this._identityComponent = ComponentFactory.getIfExists( options?.identityComponentType ?? "identity-service" ); @@ -402,8 +402,8 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon } const contextIds = await ContextIdStore.getContextIds(); - ContextIdHelper.guard(contextIds, ContextIdKeys.User); - const subject = contextIds[ContextIdKeys.User]; + ContextIdHelper.guard(contextIds, ContextIdKeys.Account); + const subject = contextIds[ContextIdKeys.Account]; const user = await this._userEntityStorage.get(subject, "userId"); if (!Is.object(user) || user.userId !== subject) { @@ -424,14 +424,16 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon await this._authenticationRateService.check("identity-adopt", subject); const identityComponent = this._identityComponent; - if (!Is.object(identityComponent)) { + if (!Is.object(identityComponent)) { throw new GeneralError( EntityStorageAuthenticationService.CLASS_NAME, "identityComponentMissing" ); } - const created = await identityComponent.identityCreateSelf(); + const created = await identityComponent.identityCreate(undefined, undefined, { + selfControlled: true + }); const documentId = created.id; if (!Is.stringValue(documentId) || !documentId.startsWith("did:")) { throw new GeneralError(EntityStorageAuthenticationService.CLASS_NAME, "identityCreateFailed"); @@ -443,7 +445,11 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon identity: documentId }); } catch (error) { - await identityComponent.identityRemove(documentId, { removeKeys: true }, documentId); + await identityComponent.identityRemove( + documentId, + { removeKeys: true, removeDocumentKey: true }, + documentId + ); throw error; } @@ -467,7 +473,7 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon // Nothing to do here, as we are stateless. // The cookie will be revoked by the REST route handling const contextIds = await ContextIdStore.getContextIds(); - const identifier = contextIds?.[ContextIdKeys.User]; + const identifier = contextIds?.[ContextIdKeys.Account]; if (Is.stringValue(identifier)) { await this._authenticationAuditService?.create({ actorId: identifier, @@ -572,9 +578,9 @@ export class EntityStorageAuthenticationService implements IAuthenticationCompon */ public async updatePassword(currentPassword: string, newPassword: string): Promise { const contextIds = await ContextIdStore.getContextIds(); - ContextIdHelper.guard(contextIds, ContextIdKeys.User); + ContextIdHelper.guard(contextIds, ContextIdKeys.Account); - const userId = contextIds[ContextIdKeys.User]; + const userId = contextIds[ContextIdKeys.Account]; await this._authenticationRateService.check("password-change", userId); const user = await this._userEntityStorage.get(userId, "userId"); 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 50220bf..e5232a3 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 @@ -91,12 +91,54 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); }); + it("should set the account key only when the user has no DID", async () => { + vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ + [ContextIdKeys.Node]: "node-1", + [ContextIdKeys.Organization]: "did:org:456" + }); + vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ + email: "user@example.com", + userId: "user-1", + organization: "did:org:456", + password: "hashed", + salt: "salt", + passwordVersion: 0 + }); + vi.spyOn(TokenHelper, "extractTokenFromHeaders").mockReturnValue({ + token: "jwt", + location: "authorization" + }); + vi.spyOn(TokenHelper, "verify").mockImplementation( + async (vault, nodeId, key, token, verifyUser) => { + const verified = await verifyUser?.("user-1", "did:org:456", undefined, 0); + expect(verified).toEqual(["user", "organization"]); + return { + header: { alg: "EdDSA" }, + payload: { sub: "user-1", org: "did:org:456" } + }; + } + ); + + await processor.start(); + + const contextIds: IContextIds = { + [ContextIdKeys.User]: "did:user:stale" + }; + const response: IHttpResponse = {}; + await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); + + expect(response.statusCode).toBeUndefined(); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBeUndefined(); + }); + it("should set tenant context from token when tenantAdminComponent is configured", async () => { const mockTenantAdminComponent = { className: vi.fn(), @@ -146,7 +188,8 @@ describe("AuthHeaderProcessor", () => { expect(response.statusCode).toBeUndefined(); expect(contextIds[ContextIdKeys.Tenant]).toBe("tenant-1"); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); }); @@ -177,6 +220,7 @@ describe("AuthHeaderProcessor", () => { expect(buildResponseSpy).toHaveBeenCalled(); expect(response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(contextIds[ContextIdKeys.Account]).toBeUndefined(); expect(contextIds[ContextIdKeys.User]).toBeUndefined(); }); @@ -251,6 +295,7 @@ describe("AuthHeaderProcessor", () => { expect(buildResponseSpy).toHaveBeenCalled(); expect(response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(contextIds[ContextIdKeys.Account]).toBeUndefined(); expect(contextIds[ContextIdKeys.User]).toBeUndefined(); expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); }); @@ -291,7 +336,8 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); @@ -337,6 +383,7 @@ describe("AuthHeaderProcessor", () => { expect(buildResponseSpy).toHaveBeenCalled(); expect(response.statusCode).toBe(HttpStatusCode.unauthorized); + expect(contextIds[ContextIdKeys.Account]).toBeUndefined(); expect(contextIds[ContextIdKeys.User]).toBeUndefined(); }); @@ -392,7 +439,8 @@ describe("AuthHeaderProcessor", () => { expect(response.statusCode).toBeUndefined(); expect(contextIds[ContextIdKeys.Tenant]).toBe("tenant-a"); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); }); @@ -434,7 +482,8 @@ describe("AuthHeaderProcessor", () => { await processor.pre({ headers: {} } as never, response, {} as never, contextIds, {}); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.Organization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); expect(contextIds[ContextIdKeys.Tenant]).toBeUndefined(); @@ -690,7 +739,8 @@ describe("AuthHeaderProcessor", () => { for (let i = 0; i < 3; i++) { const { response, contextIds } = await runRequest(authProcessor); expect(response.statusCode).toBeUndefined(); - expect(contextIds[ContextIdKeys.User]).toBe("user-1"); + expect(contextIds[ContextIdKeys.Account]).toBe("user-1"); + expect(contextIds[ContextIdKeys.User]).toBe("did:user:123"); expect(contextIds[ContextIdKeys.UserOrganization]).toBe("did:org:456"); } diff --git a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts index 2b10167..997e990 100644 --- a/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts +++ b/packages/api-auth-entity-storage-service/tests/routes/entityStorageAuthenticationRoutes.spec.ts @@ -9,7 +9,10 @@ import { import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import { HeaderTypes } from "@3sixty/web"; import { AuthHeaderProcessor } from "../../src/processors/authHeaderProcessor.js"; -import { authenticationAdoptIdentity } from "../../src/routes/entityStorageAuthenticationRoutes.js"; +import { + authenticationAdoptIdentity, + generateRestRoutesAuthentication +} from "../../src/routes/entityStorageAuthenticationRoutes.js"; describe("authenticationAdoptIdentity", () => { beforeEach(() => { @@ -77,3 +80,18 @@ describe("authenticationAdoptIdentity", () => { expect(response.headers?.[HeaderTypes.SetCookie]).toBeUndefined(); }); }); + +describe("authentication routes before a DID exists", () => { + it("should stay authenticated without an authorization subject", () => { + const routes = generateRestRoutesAuthentication("/authentication", "authentication"); + const updatePassword = routes.find( + route => route.operationId === "authenticationUpdatePassword" + ); + const adoptIdentity = routes.find(route => route.operationId === "authenticationAdoptIdentity"); + + expect(updatePassword?.requiresAuthorization).toBe(false); + expect(updatePassword?.skipAuth).toBeUndefined(); + expect(adoptIdentity?.requiresAuthorization).toBe(false); + expect(adoptIdentity?.skipAuth).toBeUndefined(); + }); +}); 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 92d867c..fe55c50 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 @@ -17,7 +17,6 @@ import { import { nameof } from "@3sixty/nameof"; import { VaultConnectorFactory, type IVaultConnector } from "@3sixty/vault-models"; import type { AuthenticationUser } from "../../src/entities/authenticationUser.js"; -import type { IIdentityAdoption } from "../../src/models/IIdentityAdoption.js"; import { initSchema } from "../../src/schema.js"; import { EntityStorageAuthenticationService } from "../../src/services/entityStorageAuthenticationService.js"; import { PasswordHelper } from "../../src/utils/passwordHelper.js"; @@ -596,7 +595,7 @@ describe("EntityStorageAuthenticationService", () => { it("should update the password for the current user", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "user-1" + [ContextIdKeys.Account]: "user-1" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue({ email: "user@example.com", @@ -625,7 +624,7 @@ describe("EntityStorageAuthenticationService", () => { it("should throw TooManyRequestsError when password change rate limit is exceeded", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "user-1" + [ContextIdKeys.Account]: "user-1" }); vi.mocked(mockAuthenticationRateService.check).mockRejectedValue( new TooManyRequestsError( @@ -645,7 +644,7 @@ describe("EntityStorageAuthenticationService", () => { it("should throw NotFoundError when updating the password for an unknown user", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "did:user:missing" + [ContextIdKeys.Account]: "user-missing" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue(undefined); @@ -657,7 +656,7 @@ describe("EntityStorageAuthenticationService", () => { it("should logout without throwing", async () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ - [ContextIdKeys.User]: "user-1" + [ContextIdKeys.Account]: "user-1" }); await expect(service.logout("token")).resolves.toBeUndefined(); @@ -789,7 +788,7 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(PasswordHelper, "updatePassword").mockResolvedValue(undefined); await ContextIdStore.run( - { [ContextIdKeys.Tenant]: TENANT_A, [ContextIdKeys.User]: "user-1" }, + { [ContextIdKeys.Tenant]: TENANT_A, [ContextIdKeys.Account]: "user-1" }, async () => { await userEntityStorage.set({ email: "user@example.com", @@ -827,7 +826,7 @@ describe("EntityStorageAuthenticationService", () => { }); await ContextIdStore.run( - { [ContextIdKeys.Tenant]: TENANT_B, [ContextIdKeys.User]: "user-1" }, + { [ContextIdKeys.Tenant]: TENANT_B, [ContextIdKeys.Account]: "user-1" }, async () => { await expect(service.updatePassword("current-password", "new-password")).rejects.toThrow( NotFoundError @@ -1023,13 +1022,17 @@ describe("EntityStorageAuthenticationService", () => { describe("adoptIdentity", () => { const mnemonic = "abandon abandon abandon abandon abandon abandon"; const documentId = "did:iota:tst:abc"; - let identity: IIdentityAdoption; + let identity: { + className: () => string; + identityCreate: ReturnType; + identityRemove: ReturnType; + }; let user: AuthenticationUser; beforeEach(async () => { identity = { className: vi.fn().mockReturnValue("IdentityService"), - identityCreateSelf: vi.fn().mockResolvedValue({ id: documentId, mnemonic }), + identityCreate: vi.fn().mockResolvedValue({ id: documentId, mnemonic }), identityRemove: vi.fn().mockResolvedValue(undefined) }; user = { @@ -1053,7 +1056,7 @@ describe("EntityStorageAuthenticationService", () => { vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1", [ContextIdKeys.Tenant]: "tenant-1", - [ContextIdKeys.User]: "user-1" + [ContextIdKeys.Account]: "user-1" }); vi.mocked(mockUserEntityStorage.get).mockResolvedValue(user); vi.spyOn(TokenHelper, "createToken").mockResolvedValue({ @@ -1066,7 +1069,9 @@ describe("EntityStorageAuthenticationService", () => { it("should store the DID on the account and leave the token subject unchanged", async () => { const result = await service.adoptIdentity(); - expect(vi.mocked(identity.identityCreateSelf)).toHaveBeenCalledTimes(1); + expect(vi.mocked(identity.identityCreate)).toHaveBeenCalledWith(undefined, undefined, { + selfControlled: true + }); expect(mockUserEntityStorage.get).toHaveBeenCalledWith("user-1", "userId"); expect(mockUserEntityStorage.set).toHaveBeenCalledWith( expect.objectContaining({ @@ -1099,23 +1104,23 @@ describe("EntityStorageAuthenticationService", () => { user.identity = "did:iota:tst:existing"; vi.spyOn(ContextIdStore, "getContextIds").mockResolvedValue({ [ContextIdKeys.Node]: "node-1", - [ContextIdKeys.User]: "user-1" + [ContextIdKeys.Account]: "user-1" }); await expect(service.adoptIdentity()).rejects.toBeInstanceOf(ConflictError); - expect(vi.mocked(identity.identityCreateSelf)).not.toHaveBeenCalled(); + expect(vi.mocked(identity.identityCreate)).not.toHaveBeenCalled(); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); expect(user.identity).toBe("did:iota:tst:existing"); }); it("should leave the stored identity unchanged when identity create throws", async () => { - vi.mocked(identity.identityCreateSelf).mockRejectedValue(new Error("publish failed")); + vi.mocked(identity.identityCreate).mockRejectedValue(new Error("publish failed")); await expect(service.adoptIdentity()).rejects.toThrow("publish failed"); expect(mockUserEntityStorage.set).not.toHaveBeenCalled(); expect(TokenHelper.createToken).not.toHaveBeenCalled(); expect(user.identity).toBeUndefined(); - expect(JSON.stringify(vi.mocked(identity.identityCreateSelf).mock.results)).not.toContain( + expect(JSON.stringify(vi.mocked(identity.identityCreate).mock.results)).not.toContain( "token" ); }); @@ -1126,7 +1131,7 @@ describe("EntityStorageAuthenticationService", () => { await expect(service.adoptIdentity()).rejects.toThrow("save failed"); expect(vi.mocked(identity.identityRemove)).toHaveBeenCalledWith( documentId, - { removeKeys: true }, + { removeKeys: true, removeDocumentKey: true }, documentId ); expect(TokenHelper.createToken).not.toHaveBeenCalled(); @@ -1135,7 +1140,7 @@ describe("EntityStorageAuthenticationService", () => { it("should keep a recorded mnemonic out of the failure", async () => { const created = { id: "not-a-did", mnemonic }; - vi.mocked(identity.identityCreateSelf).mockResolvedValue(created); + vi.mocked(identity.identityCreate).mockResolvedValue(created); const rejection = await service.adoptIdentity().then( () => "", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 787e2f1..4a6da73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -362,50 +362,53 @@ importers: version: link:../api-auth-models '@3sixty/api-models': specifier: next - version: 0.20.0-next.1 + version: 0.20.0-next.2 '@3sixty/authorization-models': specifier: next - version: 0.20.0-next.1 + version: 0.20.0-next.2 '@3sixty/context': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@3sixty/core': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@3sixty/crypto': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@3sixty/entity': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@3sixty/entity-storage-models': specifier: next - version: 0.20.0-next.1 + version: 0.20.0-next.2 + '@3sixty/identity-models': + specifier: next + version: 0.20.0-next.3 '@3sixty/nameof': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@3sixty/vault-models': specifier: next - version: 0.20.0-next.1 + version: 0.20.0-next.2 '@3sixty/web': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 devDependencies: '@3sixty/entity-storage-connector-memory': specifier: next - version: 0.20.0-next.1 + version: 0.20.0-next.2 '@3sixty/nameof-transformer': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@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)) + version: 0.20.0-next.4(@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 + version: 0.20.0-next.6 '@3sixty/validate-locales': specifier: next - version: 0.20.0-next.2 + version: 0.20.0-next.4 '@types/node': specifier: 26.6.5 version: 26.6.5 @@ -639,10 +642,18 @@ packages: resolution: {integrity: sha512-1KmZs1JOKJKqo1UEpIgWblBcnefBQ/L027HPcck7gqYp/pDJT93/OOPPv0VwHccDY4C/UN0ZhC1oD4TPv+2k3Q==} engines: {node: '>=24.0.0'} + '@3sixty/api-models@0.20.0-next.2': + resolution: {integrity: sha512-gj6Ah0yXALZk2QPukwPbDiLCozP4hi1sAJck6zGD6X9ZmKyAGu7KX/X0XSMcrw9qmbXYNpfNGB2uBtxblP8h/g==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/authorization-models@0.20.0-next.1': resolution: {integrity: sha512-t8MsVzRm6AmlNUtENJLym7bW0zR8dwqZ920gErKRFGknbNH4eoh5fmxLH+4zA1EOmgohn//xSKP8UR9uxdRHNA==} engines: {node: '>=24.0.0'} + '@3sixty/authorization-models@0.20.0-next.2': + resolution: {integrity: sha512-cfRxqanagQGrl0RZHyIxlnI44BlEhkEZ3EzOYgUm6S1HsT+R9SDqoDLdFCC+uuWGpgjcpRcmEeUHxakSJDyYQQ==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/background-task-models@0.20.0-next.1': resolution: {integrity: sha512-GUfVa5aIINeKCU7FOFiFj433BlkY5LF+mNN6ZrlPCGFE2n68bzHL8romVw1RJxb6ocVIXgt7nJ0XWMO2+ALHXA==} engines: {node: '>=24.0.0'} @@ -651,77 +662,164 @@ packages: resolution: {integrity: sha512-b9WwgWVJOqCHQBqoaWOXuWSJ2qBrWEsy1hARkMrUOtWe0sy0DnmLf4+8pVyQZgvb96pZtWWTgqZpAH2nKqi9kg==} engines: {node: '>=24.0.0'} + '@3sixty/cli-core@0.20.0-next.4': + resolution: {integrity: sha512-25b96pbRUnrB8wqoFSmO58QfmcRO+RWB1otyK6bY/3YjdCJ0MO3MsJzSbaax7S1ss+3aLHtg2fAhigOhSoAgCg==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/context@0.20.0-next.2': resolution: {integrity: sha512-tE+OtETiyPQGMeBRNILDCy4TrT4b00tuF4xWTLfx82Pr8poiHnXuRGWMHvaZ/9hHmRuYri7cAjpOt81USxK9RQ==} engines: {node: '>=24.0.0'} + '@3sixty/context@0.20.0-next.4': + resolution: {integrity: sha512-i6AcisUk6Ne4YCop9OunIDZJ8lHS8jDQzGuODfYdjDBWNZLax4EzX1EvBPdNKP4MKG3HqP02yFGHoc0Re02NXQ==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/core@0.20.0-next.2': resolution: {integrity: sha512-zoILg1ni4GXeWGvSJMJbL4YcMJmtO+/U4BqQo6EzyRu/ChgKfWi9mZoMawCsn4iZh0R6w3Dm8pqQhyaZX+dVJg==} engines: {node: '>=24.0.0'} + '@3sixty/core@0.20.0-next.4': + resolution: {integrity: sha512-gjcsZLw7ItroZy/CDcwOkEDwlDY/wZiZfGWp15/BrXPYLxmYOvMNAYrF7yhVyN+ULGy6pcxlZf0h1oRg7GnXXQ==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/crypto@0.20.0-next.2': resolution: {integrity: sha512-0tLDaAs5j3NDob9PcmTGP7SBBr2BUnmmgj7/EuvGZNXB1qDV9+ZlYbI2R6KSf8FtGs+S1VsOohgRoqIIBoGvgQ==} engines: {node: '>=24.0.0'} + '@3sixty/crypto@0.20.0-next.4': + resolution: {integrity: sha512-I4itGN4AtPwng6RK8k3hR11mVtJb4g3jOBLsxfsNOku2WDzsXdD+NT9kZ9sBtC09Gal70/4s6xV/5t4fK78cGw==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + + '@3sixty/data-core@0.20.0-next.4': + resolution: {integrity: sha512-pXwWiwg6puOdrVAJoA/ZEN0JeOYKP9kzog75cjgPsBf391sRYdDwpS6eYXuE+uOkaxv9dOPsBvSONoIZjhq5hQ==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + + '@3sixty/data-json-ld@0.20.0-next.4': + resolution: {integrity: sha512-xUQU4EhlkIShuv59KuZfm2yMgRIBHWO6kc+grENZmZTt41rL0sAnhGy8fxsqRWW6DYmpsPZ5exmqUVHKoNtySA==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@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-connector-memory@0.20.0-next.2': + resolution: {integrity: sha512-LDPSydkp9pRXlSRGA0zcwDiUg2P6sQTuJjnpOVqc5ZGtezzx4wtahQMUKpeMwlt5+Lr15iiwfxe+tb/dXtVu/Q==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@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-storage-models@0.20.0-next.2': + resolution: {integrity: sha512-X4HpqwQCaCygHAh/YfaHvihLw7ce3d1yIzloiPpz8MjClTKWAZSgppg/+ZmWb1pKikPqqAvKx/P0w052BOrv5w==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/entity@0.20.0-next.2': resolution: {integrity: sha512-iq8T0sVI+FGheo7SXmoEPtUAgSIvej7NSy06vD3od5a84WyHzTHsf4nlvV97VDegFyUxXjAhKS5WjLK8tbjFdA==} engines: {node: '>=24.0.0'} + '@3sixty/entity@0.20.0-next.4': + resolution: {integrity: sha512-GUgvE4EA3SOIrHWz7btls7jAI8FoVL/N0wlqP/S/liCtcDzDBN8EkJjzeQ/3LYEr209x7F/RcP5Jn0UNF/D79Q==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + + '@3sixty/identity-models@0.20.0-next.3': + resolution: {integrity: sha512-eTipJ7ldva0JWBBnbpptaktzbn4q24UZtAqXSY0qvLY+PEggnOBS7RGxA6J8FO5Sj1OZ7uYY3bT3FUbCi4Hr4w==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/logging-models@0.20.0-next.1': resolution: {integrity: sha512-PvrnZSBuws7TML96voAcKN7jZqMSbMKyq3IXh3dZ1zpgU6U7iBsqT5ynfOln2P3vkVdD29ohFOrX1zr5NPsX8Q==} engines: {node: '>=24.0.0'} + '@3sixty/logging-models@0.20.0-next.2': + resolution: {integrity: sha512-MLUhNB6oojDYu/38fDs6M+XKkpdkeLblglylOfuvd30DSz1nJ8UBQ/HY/Eu4dNClXqHlYSLu8siyzwpgpL+6UA==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/nameof-transformer@0.20.0-next.2': resolution: {integrity: sha512-5bcCOCohanUx81q/R3Q7KeirNbJJpysFv+PO+yb3Tq8J6xbujAEilG8xQc41zszVBOI9LbQcSmppn2FEKhMMJw==} engines: {node: '>=24.0.0'} hasBin: true + '@3sixty/nameof-transformer@0.20.0-next.4': + resolution: {integrity: sha512-ru3SW/LCzSA3CTOED9EMadnJxFbpCQq6TNuzbf/hCmwWVbZmYCp0zjQ28symTmDlEl3uSsPLfKXXSWQhhtZlyQ==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + hasBin: true + '@3sixty/nameof-vitest-plugin@0.20.0-next.2': resolution: {integrity: sha512-BNVlzVpobTayWnS01pX83dSGu/edZepztxOojmXHWLrxYfLM9iUeDRniHcH3VdZqz5hm3Sp6UH66BcqUqVcgOw==} engines: {node: '>=24.0.0'} + '@3sixty/nameof-vitest-plugin@0.20.0-next.4': + resolution: {integrity: sha512-lE9YgvJoUDQiAVsC97tri97hfCNoGLGOSJ4x0mHnFx10j0TYyW37ztoXOwhGrJynqFEyB8tX1A/XHo2rxWwNjA==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/nameof@0.20.0-next.2': resolution: {integrity: sha512-isPi+GgUWa/B3+gtLbPm3HJ5yaMJrp2uMlzLIkC2aBGQGC2jFmDk5HSmbpJg7mhQf38YomwI6AzC2JaZcL1XWQ==} engines: {node: '>=24.0.0'} + '@3sixty/nameof@0.20.0-next.4': + resolution: {integrity: sha512-vERokiuGRph0oESD71WqFqqiTua4vSddovkUQSPt3WrMC5GRRvi1nJCnkDyLKQsCiKnHXwBg7ebTjPGluVjrKw==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + + '@3sixty/standards-w3c-did@0.20.0-next.3': + resolution: {integrity: sha512-/snKyKTpq+DJdr4Q+noNpH2aRurJnwzt06Z1hf1Zf6gqfD1pDGajKIgRuvSUsg4CDF8pRMNY4imR8hhCXK9U9g==} + engines: {bun: '>=1.4.4', 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/telemetry-models@0.20.0-next.2': + resolution: {integrity: sha512-l1vx6/Hic4PrNiKlmQzuTjM2n/kXg6JXyPnXbaiLVrPVAwOqLhAKuglBFrkUoc/QdQ04yeO7oSqBQdsRr4YEfg==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/tools-core@0.20.0-next.4': resolution: {integrity: sha512-M/NKlmAvQoOn/ailU8yhjjINjP4nEqR3YG0qiYauxhyMOwQJLqcfXoVCo4zzAURcBcb+SY3YuYR07dG3VAsqTg==} engines: {node: '>=24.0.0'} + '@3sixty/tools-core@0.20.0-next.6': + resolution: {integrity: sha512-h3tcgTgjpA61pWHj1f58LDZCfK7kDwO7pQhjyIP1YLLs3gLAEdukhZFa5UELl0HKv0NyR7oQLK+7LTPKlEjUdw==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/tools-models@0.20.0-next.4': resolution: {integrity: sha512-/Ehlhy4Hy5IArm9WJEMs8P42jFBuOwu3YSDiqKl3iAZulgcGrX9AHmIEWgx9nUuWNAZ8646sQwcIQdMNTcwsxQ==} engines: {node: '>=24.0.0'} + '@3sixty/tools-models@0.20.0-next.6': + resolution: {integrity: sha512-qohLvl5yyewAc5lEyCaKUXaym2mpe9LJoe+5PsSLL67rbgimNhkz4zbRHl1XPrHD9F8kNU3jqP8sT3tLIQf6Zg==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@3sixty/ts-to-openapi@0.20.0-next.4': resolution: {integrity: sha512-vdo/d7mZn5/ysnS0Qq0GdBA26F943kBd3e73l9oe7la6WQ0PKr0sKSu/FfeJJHUvi5+Hlpno6kRvmI3ndR8DkA==} engines: {node: '>=24.0.0'} hasBin: true + '@3sixty/ts-to-openapi@0.20.0-next.6': + resolution: {integrity: sha512-WUh0VZ6Q5rmXN6A7QtRap2Igovaz78TwmahXDxwZ6Pem8OB1ShcgClBhIPiSJqhBlRtdwEpZyk9TKK9X5loG1Q==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + hasBin: true + '@3sixty/validate-locales@0.20.0-next.2': resolution: {integrity: sha512-YdHAAyTWrsSv1OWwU//FBpn4YgW1S3z7PYHczcr/GXdfZ26ormHug6Bk9nugtJVb/1MVGkvk4nsq6LtdbsO70w==} engines: {node: '>=24.0.0'} hasBin: true - '@3sixty/vault-models@0.20.0-next.1': - resolution: {integrity: sha512-w3rNruwfhaiNpkIopMuD3Yn/5vTyfCWntJ2sJu/xT5nrUyqGm3GkgvbkTZeF7q9Q3nuvQRt3nWkdSjUM3cZoTw==} - engines: {node: '>=24.0.0'} + '@3sixty/validate-locales@0.20.0-next.4': + resolution: {integrity: sha512-jz17RYBY9P60anDy/J/6Uhr06UDDzlJSMxadZQh3s2zsyj45NMtpqBp9nwVV2XOWTWE5ljNdApFC/b94ZbfJiA==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + hasBin: true + + '@3sixty/vault-models@0.20.0-next.2': + resolution: {integrity: sha512-Ca7xajtfmbLxSSX6EFhyUhgqCwH/U0hcN3yeF5kjfOogi2DYHXrVe1Pi5x3dHS4PrMFYK0LWJXHlJ0Wr6DwoVA==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} '@3sixty/web@0.20.0-next.2': resolution: {integrity: sha512-fnX4iTbgaUa5x8RzppQ+GqvP4xex38M8kK48X0YGLMBfKBoU4iolZl1w4WXxRIipVjjZKvr51Bwwf0TW5UGcMg==} engines: {node: '>=24.0.0'} + '@3sixty/web@0.20.0-next.4': + resolution: {integrity: sha512-OAqSjTvA2JKpBnbHZ475QWlTqOjo79K0JMcFok/3llOmUUz+ikBCaG5S3EiCF7CNefE8xLgkOKPil2CX66EXdw==} + engines: {bun: '>=1.4.4', node: '>=24.0.0'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -1075,6 +1173,10 @@ packages: resolution: {integrity: sha512-8NvDv8h3eaxejAvD+crMsSEgGq6LiunfS6iD2TBAcDqskNsIpN43AMHNNRf7T7Fd/3Y413nQDBH4RmsHcNsiqg==} engines: {node: '>=22.18.0'} + '@digitalbazaar/http-client@4.4.0': + resolution: {integrity: sha512-ODhCGmElUPmR3IR+KZmBNkRAFyjJ01rxvk2E+/qQ2h2EGPJH5k6bz3N24ympGc5+i4YCGk/ipIpmkwc0+iSmRg==} + engines: {node: '>=18.0'} + '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} @@ -1483,6 +1585,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonld@1.5.15': + resolution: {integrity: sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==} + '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} @@ -1861,6 +1966,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -1981,6 +2094,10 @@ packages: caniuse-lite@1.0.30001816: resolution: {integrity: sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==} + canonicalize@2.1.0: + resolution: {integrity: sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==} + hasBin: true + chai@6.3.0: resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} engines: {node: '>=18'} @@ -2794,6 +2911,10 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonld@9.0.0: + resolution: {integrity: sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==} + engines: {node: '>=18'} + jsonpath-plus@10.4.0: resolution: {integrity: sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==} engines: {node: '>=18.0.0'} @@ -2814,6 +2935,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + ky@1.14.3: + resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==} + engines: {node: '>=18'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -3297,6 +3422,10 @@ packages: resolution: {integrity: sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==} engines: {node: '>=22'} + rdf-canonize@5.0.0: + resolution: {integrity: sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==} + engines: {node: '>=18'} + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -3402,6 +3531,9 @@ packages: engines: {node: '>=10'} hasBin: true + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3649,6 +3781,10 @@ packages: undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + undici@6.29.1: + resolution: {integrity: sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==} + engines: {node: '>=18.17'} + unicorn-magic@0.4.1: resolution: {integrity: sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==} engines: {node: '>=20'} @@ -3903,12 +4039,25 @@ snapshots: '@3sixty/nameof': 0.20.0-next.2 '@3sixty/web': 0.20.0-next.2 + '@3sixty/api-models@0.20.0-next.2': + dependencies: + '@3sixty/context': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/entity': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/web': 0.20.0-next.4 + '@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/authorization-models@0.20.0-next.2': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/telemetry-models': 0.20.0-next.2 + '@3sixty/background-task-models@0.20.0-next.1': dependencies: '@3sixty/core': 0.20.0-next.2 @@ -3923,17 +4072,35 @@ snapshots: commander: 15.0.0 dotenv: 18.0.8 + '@3sixty/cli-core@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + chalk: 6.0.1 + commander: 15.0.0 + dotenv: 18.0.8 + '@3sixty/context@0.20.0-next.2': dependencies: '@3sixty/core': 0.20.0-next.2 '@3sixty/nameof': 0.20.0-next.2 + '@3sixty/context@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/core@0.20.0-next.2': dependencies: '@3sixty/nameof': 0.20.0-next.2 intl-messageformat: 12.1.4 rfc6902: 5.3.0 + '@3sixty/core@0.20.0-next.4': + dependencies: + '@3sixty/nameof': 0.20.0-next.4 + intl-messageformat: 12.1.4 + rfc6902: 5.3.0 + '@3sixty/crypto@0.20.0-next.2': dependencies: '@3sixty/core': 0.20.0-next.2 @@ -3946,6 +4113,37 @@ snapshots: '@scure/bip39': 2.4.0 micro-key-producer: 0.10.2 + '@3sixty/crypto@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@noble/ciphers': 2.4.0 + '@noble/curves': 2.4.0 + '@noble/hashes': 2.4.0 + '@scure/base': 2.4.0 + '@scure/bip32': 2.4.0 + '@scure/bip39': 2.4.0 + micro-key-producer: 0.10.2 + + '@3sixty/data-core@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/crypto': 0.20.0-next.4 + '@3sixty/entity': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/web': 0.20.0-next.4 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + + '@3sixty/data-json-ld@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/data-core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/web': 0.20.0-next.4 + '@types/jsonld': 1.5.15 + jsonld: 9.0.0 + '@3sixty/entity-storage-connector-memory@0.20.0-next.1': dependencies: '@3sixty/api-models': 0.20.0-next.1 @@ -3956,6 +4154,16 @@ snapshots: '@3sixty/logging-models': 0.20.0-next.1 '@3sixty/nameof': 0.20.0-next.2 + '@3sixty/entity-storage-connector-memory@0.20.0-next.2': + dependencies: + '@3sixty/api-models': 0.20.0-next.2 + '@3sixty/context': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/entity': 0.20.0-next.4 + '@3sixty/entity-storage-models': 0.20.0-next.2 + '@3sixty/logging-models': 0.20.0-next.2 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/entity-storage-models@0.20.0-next.1': dependencies: '@3sixty/context': 0.20.0-next.2 @@ -3965,6 +4173,15 @@ snapshots: '@3sixty/logging-models': 0.20.0-next.1 '@3sixty/nameof': 0.20.0-next.2 + '@3sixty/entity-storage-models@0.20.0-next.2': + dependencies: + '@3sixty/context': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/crypto': 0.20.0-next.4 + '@3sixty/entity': 0.20.0-next.4 + '@3sixty/logging-models': 0.20.0-next.2 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/entity@0.20.0-next.2': dependencies: '@3sixty/core': 0.20.0-next.2 @@ -3972,17 +4189,45 @@ snapshots: reflect-metadata: 0.2.2 tslib: 2.8.1 + '@3sixty/entity@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + reflect-metadata: 0.2.2 + tslib: 2.8.1 + + '@3sixty/identity-models@0.20.0-next.3': + dependencies: + '@3sixty/context': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/data-json-ld': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/standards-w3c-did': 0.20.0-next.3 + '@3sixty/telemetry-models': 0.20.0-next.2 + '@3sixty/web': 0.20.0-next.4 + '@3sixty/logging-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/logging-models@0.20.0-next.2': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/entity': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/nameof-transformer@0.20.0-next.2': dependencies: typescript: 6.0.3 typescript-native: typescript@7.1.0-beta + '@3sixty/nameof-transformer@0.20.0-next.4': + dependencies: + typescript: 6.0.3 + typescript-native: typescript@7.1.0-beta + '@3sixty/nameof-vitest-plugin@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))': dependencies: '@3sixty/nameof-transformer': 0.20.0-next.2 @@ -4002,14 +4247,49 @@ snapshots: - msw - vite + '@3sixty/nameof-vitest-plugin@0.20.0-next.4(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0))': + dependencies: + '@3sixty/nameof-transformer': 0.20.0-next.4 + vitest: 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)) + transitivePeerDependencies: + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@vitest/browser-playwright' + - '@vitest/browser-preview' + - '@vitest/browser-webdriverio' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - happy-dom + - jsdom + - msw + - vite + '@3sixty/nameof@0.20.0-next.2': {} + '@3sixty/nameof@0.20.0-next.4': {} + + '@3sixty/standards-w3c-did@0.20.0-next.3': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/crypto': 0.20.0-next.4 + '@3sixty/data-core': 0.20.0-next.4 + '@3sixty/data-json-ld': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/web': 0.20.0-next.4 + '@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/telemetry-models@0.20.0-next.2': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/tools-core@0.20.0-next.4': dependencies: '@3sixty/core': 0.20.0-next.2 @@ -4019,11 +4299,22 @@ snapshots: glob: 13.0.6 typescript: 6.0.3 + '@3sixty/tools-core@0.20.0-next.6': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/crypto': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/tools-models': 0.20.0-next.6 + glob: 13.0.6 + typescript: 6.0.3 + '@3sixty/tools-models@0.20.0-next.4': dependencies: '@3sixty/core': 0.20.0-next.2 '@3sixty/nameof': 0.20.0-next.2 + '@3sixty/tools-models@0.20.0-next.6': {} + '@3sixty/ts-to-openapi@0.20.0-next.4': dependencies: '@3sixty/cli-core': 0.20.0-next.2 @@ -4035,6 +4326,16 @@ snapshots: commander: 15.0.0 glob: 13.0.6 + '@3sixty/ts-to-openapi@0.20.0-next.6': + dependencies: + '@3sixty/cli-core': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/tools-core': 0.20.0-next.6 + '@3sixty/tools-models': 0.20.0-next.6 + '@3sixty/web': 0.20.0-next.4 + commander: 15.0.0 + '@3sixty/validate-locales@0.20.0-next.2': dependencies: '@3sixty/cli-core': 0.20.0-next.2 @@ -4045,11 +4346,21 @@ snapshots: glob: 13.0.6 typescript: 6.0.3 - '@3sixty/vault-models@0.20.0-next.1': + '@3sixty/validate-locales@0.20.0-next.4': dependencies: - '@3sixty/core': 0.20.0-next.2 - '@3sixty/nameof': 0.20.0-next.2 - '@3sixty/web': 0.20.0-next.2 + '@3sixty/cli-core': 0.20.0-next.4 + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/nameof-transformer': 0.20.0-next.4 + commander: 15.0.0 + glob: 13.0.6 + typescript: 6.0.3 + + '@3sixty/vault-models@0.20.0-next.2': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + '@3sixty/web': 0.20.0-next.4 '@3sixty/web@0.20.0-next.2': dependencies: @@ -4058,6 +4369,13 @@ snapshots: '@3sixty/nameof': 0.20.0-next.2 jose: 6.2.12 + '@3sixty/web@0.20.0-next.4': + dependencies: + '@3sixty/core': 0.20.0-next.4 + '@3sixty/crypto': 0.20.0-next.4 + '@3sixty/nameof': 0.20.0-next.4 + jose: 6.2.12 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -4435,6 +4753,11 @@ snapshots: '@cspell/url@10.4.0': {} + '@digitalbazaar/http-client@4.4.0': + dependencies: + ky: 1.14.3 + undici: 6.29.1 + '@dual-bundle/import-meta-resolve@4.2.1': {} '@emnapi/core@1.10.0': @@ -4809,6 +5132,8 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/jsonld@1.5.15': {} + '@types/katex@0.16.8': {} '@types/minimist@1.2.5': {} @@ -5090,6 +5415,10 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -5200,6 +5529,8 @@ snapshots: caniuse-lite@1.0.30001816: {} + canonicalize@2.1.0: {} + chai@6.3.0: {} chalk-template@1.1.3: @@ -6047,6 +6378,13 @@ snapshots: jsonc-parser@3.3.1: {} + jsonld@9.0.0: + dependencies: + '@digitalbazaar/http-client': 4.4.0 + canonicalize: 2.1.0 + lru-cache: 6.0.0 + rdf-canonize: 5.0.0 + jsonpath-plus@10.4.0: dependencies: '@jsep-plugin/assignment': 1.4.0(jsep@1.6.0) @@ -6065,6 +6403,8 @@ snapshots: kind-of@6.0.3: {} + ky@1.14.3: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6660,6 +7000,10 @@ snapshots: quote-js-string@0.1.0: {} + rdf-canonize@5.0.0: + dependencies: + setimmediate: 1.0.5 + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -6810,6 +7154,8 @@ snapshots: semver@7.8.5: {} + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -7041,6 +7387,8 @@ snapshots: undici-types@8.9.0: {} + undici@6.29.1: {} + unicorn-magic@0.4.1: {} unist-util-is@4.1.0: {}