@owlmeans/test-auth 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @owlmeans/test-auth
2
+
3
+ The **only** OwlMeans package that ships authentication/authorization mocks. Tests in any other package may import from here when they need a fake authenticated identity, a deterministic Ed25519 keypair, or an in-memory `TRUSTED` resource. No other mocks (database, network, sibling-package services) belong in test code — those packages get integration tests instead. See the `auth-protocol` skill for the protocol this mock implements.
4
+
5
+ Helpers exported here:
6
+
7
+ - `makeFixtureKeyPair(seed?)` — deterministic Ed25519 `KeyPairModel`.
8
+ - `makeMemoryTrustedResource(records?)` — `Resource<TrustedRecord>` satisfying `trust()` lookups against the `TRUSTED` config resource.
9
+ - `makeMockGuard({ alias?, auth?, allow? })` — `GuardService` that resolves to a chosen `Auth`. Implements `match`, `handle`, `authenticated`.
10
+ - `withAuth(ctx, auth)` — convenience that registers a mock guard with a chosen `Auth` on the context.
11
+ - `signMockEnvelope(msg, type, kind?, kp?)` — wraps `makeEnvelopeModel` with a fixture keypair to produce a signed envelope.
12
+ - `makeBearer(auth, kp?)` — `ED25519-BASIC-TOKEN <encoded>` header value for unit tests of header parsing.
13
+ - Canonical fixtures: `SUPERUSER`, `USER`, `SERVICE` `Auth` payloads.
@@ -0,0 +1,16 @@
1
+ import { EnvelopeKind } from '@owlmeans/basic-envelope';
2
+ import type { KeyPairModel } from '@owlmeans/basic-keys';
3
+ import type { Auth } from '@owlmeans/auth';
4
+ /**
5
+ * Wrap a payload in a signed `EnvelopeModel` using a fixture keypair (or one
6
+ * supplied by the caller) and serialize it. Useful in category-B tests that
7
+ * exercise envelope unwrapping/verification without hitting a real signer.
8
+ */
9
+ export declare const signMockEnvelope: <T extends {} | string>(msg: T, type: string, kind?: EnvelopeKind, kp?: KeyPairModel) => Promise<string>;
10
+ /**
11
+ * Produce an `Authorization` header value of the form
12
+ * `ED25519-BASIC-TOKEN <encoded>` for a given `Auth`. Lets unit tests of
13
+ * header parsing and guard `match` logic generate valid-looking bearers.
14
+ */
15
+ export declare const makeBearer: (auth: Auth, kp?: KeyPairModel) => Promise<string>;
16
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAExD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAK1C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAU,CAAC,SAAS,EAAE,GAAG,MAAM,EAC1D,KAAK,CAAC,EACN,MAAM,MAAM,EACZ,OAAM,YAAiC,EACvC,KAAI,YAAsD,KACzD,OAAO,CAAC,MAAM,CAKhB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,IAAI,EACV,KAAK,YAAY,KAChB,OAAO,CAAC,MAAM,CAQhB,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope';
2
+ import { AuthroizationType } from '@owlmeans/auth';
3
+ import { makeFixtureKeyPair } from './keys.js';
4
+ const AUTH_BEARER_PREFIX = AuthroizationType.Ed25519BasicToken.toUpperCase();
5
+ /**
6
+ * Wrap a payload in a signed `EnvelopeModel` using a fixture keypair (or one
7
+ * supplied by the caller) and serialize it. Useful in category-B tests that
8
+ * exercise envelope unwrapping/verification without hitting a real signer.
9
+ */
10
+ export const signMockEnvelope = async (msg, type, kind = EnvelopeKind.Token, kp = makeFixtureKeyPair('test-auth-default')) => {
11
+ const envelope = makeEnvelopeModel(type);
12
+ envelope.send(msg);
13
+ const signed = await envelope.sign(kp, kind);
14
+ return signed;
15
+ };
16
+ /**
17
+ * Produce an `Authorization` header value of the form
18
+ * `ED25519-BASIC-TOKEN <encoded>` for a given `Auth`. Lets unit tests of
19
+ * header parsing and guard `match` logic generate valid-looking bearers.
20
+ */
21
+ export const makeBearer = async (auth, kp) => {
22
+ const encoded = await signMockEnvelope(auth, AuthroizationType.Ed25519BasicToken, EnvelopeKind.Token, kp);
23
+ return `${AUTH_BEARER_PREFIX} ${encoded}`;
24
+ };
25
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,iBAAiB,CAAC,WAAW,EAAE,CAAA;AAE5E;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EACnC,GAAM,EACN,IAAY,EACZ,OAAqB,YAAY,CAAC,KAAK,EACvC,KAAmB,kBAAkB,CAAC,mBAAmB,CAAC,EACzC,EAAE;IACnB,MAAM,QAAQ,GAAG,iBAAiB,CAAI,IAAI,CAAC,CAAA;IAC3C,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAC5C,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,IAAU,EACV,EAAiB,EACA,EAAE;IACnB,MAAM,OAAO,GAAG,MAAM,gBAAgB,CACpC,IAA0C,EAC1C,iBAAiB,CAAC,iBAAiB,EACnC,YAAY,CAAC,KAAK,EAClB,EAAE,CACH,CAAA;IACD,OAAO,GAAG,kBAAkB,IAAI,OAAO,EAAE,CAAA;AAC3C,CAAC,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { Auth } from '@owlmeans/auth';
2
+ export declare const SUPERUSER: Auth;
3
+ export declare const USER: Auth;
4
+ export declare const SERVICE: Auth;
5
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAY1C,eAAO,MAAM,SAAS,EAAE,IAAqE,CAAA;AAC7F,eAAO,MAAM,IAAI,EAAE,IAAsD,CAAA;AACzE,eAAO,MAAM,OAAO,EAAE,IAA+D,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { AuthRole, AuthenticationType } from '@owlmeans/auth';
2
+ const baseAuth = (role, userId, token) => ({
3
+ type: AuthenticationType.BasicEd25519,
4
+ role,
5
+ userId,
6
+ token,
7
+ isUser: role !== AuthRole.Service && role !== AuthRole.System,
8
+ createdAt: new Date(0),
9
+ scopes: ['*'],
10
+ });
11
+ export const SUPERUSER = baseAuth(AuthRole.Superuser, 'superuser-1', 'token-superuser');
12
+ export const USER = baseAuth(AuthRole.User, 'user-1', 'token-user');
13
+ export const SERVICE = baseAuth(AuthRole.Service, 'service-1', 'token-service');
14
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG7D,MAAM,QAAQ,GAAG,CAAC,IAAc,EAAE,MAAc,EAAE,KAAa,EAAQ,EAAE,CAAC,CAAC;IACzE,IAAI,EAAE,kBAAkB,CAAC,YAAY;IACrC,IAAI;IACJ,MAAM;IACN,KAAK;IACL,MAAM,EAAE,IAAI,KAAK,QAAQ,CAAC,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC,MAAM;IAC7D,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC;IACtB,MAAM,EAAE,CAAC,GAAG,CAAC;CACd,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,SAAS,GAAS,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,iBAAiB,CAAC,CAAA;AAC7F,MAAM,CAAC,MAAM,IAAI,GAAS,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAA;AACzE,MAAM,CAAC,MAAM,OAAO,GAAS,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,eAAe,CAAC,CAAA"}
@@ -0,0 +1,30 @@
1
+ import type { BasicConfig, BasicContext } from '@owlmeans/context';
2
+ import type { Auth } from '@owlmeans/auth';
3
+ import type { AbstractRequest, AbstractResponse, GuardService } from '@owlmeans/entrypoint';
4
+ export interface MockGuardOptions {
5
+ alias?: string;
6
+ /** Auth payload that `handle` will resolve into the response. */
7
+ auth?: Auth;
8
+ /** Optional predicate that decides whether `match` reports a hit. Defaults to "always match". */
9
+ allow?: (req: AbstractRequest, res: AbstractResponse<unknown>) => boolean | Promise<boolean>;
10
+ /** Token returned by the client-side `authenticated()` method. */
11
+ token?: string;
12
+ }
13
+ /**
14
+ * Build a `GuardService` that resolves to a chosen `Auth` without exercising
15
+ * any real cryptography or trusted-record lookup. Use this in category-B
16
+ * tests where the goal is to verify behaviour that depends on an
17
+ * already-authenticated identity.
18
+ *
19
+ * The mock implements the full `GuardService` shape — `match`, `handle`,
20
+ * `authenticated` — so it can be registered on a context exactly the way
21
+ * a real guard is.
22
+ */
23
+ export declare const makeMockGuard: (opts?: MockGuardOptions) => GuardService;
24
+ /**
25
+ * Register a mock guard on `context` that resolves to the given `auth`.
26
+ * Returns the same context for chaining. Existing guards under the same
27
+ * alias are not replaced — register before any real guard service.
28
+ */
29
+ export declare const withAuth: <C extends BasicConfig, T extends BasicContext<C>>(context: T, auth: Auth, alias?: string) => T;
30
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,iGAAiG;IACjG,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5F,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GAAI,OAAM,gBAAqB,KAAG,YAsB3D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EACvE,SAAS,CAAC,EACV,MAAM,IAAI,EACV,QAAO,MAAsB,KAC5B,CAGF,CAAA"}
package/build/guard.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createService } from '@owlmeans/context';
2
+ import { DEFAULT_GUARD } from '@owlmeans/auth-common';
3
+ /**
4
+ * Build a `GuardService` that resolves to a chosen `Auth` without exercising
5
+ * any real cryptography or trusted-record lookup. Use this in category-B
6
+ * tests where the goal is to verify behaviour that depends on an
7
+ * already-authenticated identity.
8
+ *
9
+ * The mock implements the full `GuardService` shape — `match`, `handle`,
10
+ * `authenticated` — so it can be registered on a context exactly the way
11
+ * a real guard is.
12
+ */
13
+ export const makeMockGuard = (opts = {}) => {
14
+ const alias = opts.alias ?? DEFAULT_GUARD;
15
+ const allow = opts.allow ?? (() => true);
16
+ const guard = createService(alias, {
17
+ token: opts.token,
18
+ match: async (req, res) => {
19
+ return Boolean(await allow(req, res));
20
+ },
21
+ handle: async (_req, res) => {
22
+ if (opts.auth != null) {
23
+ res.resolve(opts.auth);
24
+ }
25
+ return opts.auth;
26
+ },
27
+ authenticated: async () => opts.token ?? null,
28
+ });
29
+ return guard;
30
+ };
31
+ /**
32
+ * Register a mock guard on `context` that resolves to the given `auth`.
33
+ * Returns the same context for chaining. Existing guards under the same
34
+ * alias are not replaced — register before any real guard service.
35
+ */
36
+ export const withAuth = (context, auth, alias = DEFAULT_GUARD) => {
37
+ const guard = makeMockGuard({ alias, auth });
38
+ return context.registerService(guard);
39
+ };
40
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAcrD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAyB,EAAE,EAAgB,EAAE;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa,CAAA;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAExC,MAAM,KAAK,GAAG,aAAa,CAAe,KAAK,EAAE;QAC/C,KAAK,EAAE,IAAI,CAAC,KAAK;QAEjB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YACxB,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACvC,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAC1B,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;gBACtB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACxB,CAAC;YACD,OAAO,IAAI,CAAC,IAAa,CAAA;QAC3B,CAAC;QAED,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI;KAC9C,CAAC,CAAA;IAEF,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,OAAU,EACV,IAAU,EACV,QAAgB,aAAa,EAC1B,EAAE;IACL,MAAM,KAAK,GAAG,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAC5C,OAAO,OAAO,CAAC,eAAe,CAAI,KAAK,CAAC,CAAA;AAC1C,CAAC,CAAA"}
@@ -0,0 +1,6 @@
1
+ export * from './keys.js';
2
+ export * from './trusted.js';
3
+ export * from './guard.js';
4
+ export * from './envelope.js';
5
+ export * from './fixtures.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA"}
package/build/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './keys.js';
2
+ export * from './trusted.js';
3
+ export * from './guard.js';
4
+ export * from './envelope.js';
5
+ export * from './fixtures.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { KeyPairModel } from '@owlmeans/basic-keys';
2
+ /**
3
+ * Deterministic Ed25519 keypair derived from `seed`. Same seed → same keys.
4
+ * Use in tests to keep signatures and trust-records stable across runs.
5
+ *
6
+ * Calling without a seed returns a fresh random keypair (re-uses
7
+ * `makeKeyPairModel()` semantics).
8
+ */
9
+ export declare const makeFixtureKeyPair: (seed?: string) => KeyPairModel;
10
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAMxD;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,YAMlD,CAAA"}
package/build/keys.js ADDED
@@ -0,0 +1,19 @@
1
+ import { makeKeyPairModel } from '@owlmeans/basic-keys';
2
+ import { createHash } from 'node:crypto';
3
+ const encodeBase64 = (bytes) => Buffer.from(bytes).toString('base64');
4
+ /**
5
+ * Deterministic Ed25519 keypair derived from `seed`. Same seed → same keys.
6
+ * Use in tests to keep signatures and trust-records stable across runs.
7
+ *
8
+ * Calling without a seed returns a fresh random keypair (re-uses
9
+ * `makeKeyPairModel()` semantics).
10
+ */
11
+ export const makeFixtureKeyPair = (seed) => {
12
+ if (seed == null)
13
+ return makeKeyPairModel();
14
+ const primary = createHash('sha256')
15
+ .update(`@owlmeans/test-auth:${seed}`)
16
+ .digest();
17
+ return makeKeyPairModel(`ed25519:${encodeBase64(primary)}`);
18
+ };
19
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,YAAY,GAAG,CAAC,KAAiB,EAAU,EAAE,CACjD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAa,EAAgB,EAAE;IAChE,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,gBAAgB,EAAE,CAAA;IAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC;SACjC,MAAM,CAAC,uBAAuB,IAAI,EAAE,CAAC;SACrC,MAAM,EAAE,CAAA;IACX,OAAO,gBAAgB,CAAC,WAAW,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;AAC7D,CAAC,CAAA"}
@@ -0,0 +1,14 @@
1
+ import type { TrustedRecord } from '@owlmeans/auth-common';
2
+ import type { Resource } from '@owlmeans/resource';
3
+ /**
4
+ * In-memory `Resource<TrustedRecord>` used to satisfy `trust()` lookups
5
+ * (`@owlmeans/auth-common/utils/trusted.ts`). Pre-populate with the
6
+ * trusted users your code under test expects to find.
7
+ *
8
+ * Only `load`, `save` and `create` are implemented — that covers the
9
+ * `trust()` boundary plus straightforward seeding from tests. Other
10
+ * operations throw, so tests that need them have to be honest about
11
+ * adding integration coverage instead of leaning on the mock.
12
+ */
13
+ export declare const makeMemoryTrustedResource: (records?: TrustedRecord[], alias?: string) => Resource<TrustedRecord>;
14
+ //# sourceMappingURL=trusted.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted.d.ts","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAWlD;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAS,aAAa,EAAO,EAC7B,QAAO,MAA8B,KACpC,QAAQ,CAAC,aAAa,CAsDxB,CAAA"}
@@ -0,0 +1,67 @@
1
+ import { appendContextual } from '@owlmeans/context';
2
+ const TRUSTED_DEFAULT_ALIAS = 'TRUSTED';
3
+ const notImplemented = (op) => {
4
+ throw new Error(`@owlmeans/test-auth: in-memory TRUSTED resource does not implement ${op}()`);
5
+ };
6
+ const getter = (record, field) => record[field];
7
+ /**
8
+ * In-memory `Resource<TrustedRecord>` used to satisfy `trust()` lookups
9
+ * (`@owlmeans/auth-common/utils/trusted.ts`). Pre-populate with the
10
+ * trusted users your code under test expects to find.
11
+ *
12
+ * Only `load`, `save` and `create` are implemented — that covers the
13
+ * `trust()` boundary plus straightforward seeding from tests. Other
14
+ * operations throw, so tests that need them have to be honest about
15
+ * adding integration coverage instead of leaning on the mock.
16
+ */
17
+ export const makeMemoryTrustedResource = (records = [], alias = TRUSTED_DEFAULT_ALIAS) => {
18
+ const store = new Map();
19
+ for (const r of records) {
20
+ if (r.id == null) {
21
+ throw new Error('@owlmeans/test-auth: TRUSTED record without id');
22
+ }
23
+ store.set(r.id, r);
24
+ }
25
+ const findByField = (value, field) => {
26
+ for (const record of store.values()) {
27
+ if (getter(record, field) === value)
28
+ return record;
29
+ }
30
+ return null;
31
+ };
32
+ const resource = {
33
+ load: async (id, field) => {
34
+ const fieldName = typeof field === 'string'
35
+ ? field
36
+ : field?.field;
37
+ if (fieldName != null && fieldName !== 'id') {
38
+ return findByField(id, fieldName) ?? null;
39
+ }
40
+ return store.get(id) ?? null;
41
+ },
42
+ save: async (record) => {
43
+ if (record.id == null) {
44
+ throw new Error('@owlmeans/test-auth: cannot save TRUSTED record without id');
45
+ }
46
+ store.set(record.id, record);
47
+ return record;
48
+ },
49
+ create: async (record) => {
50
+ if (record.id == null) {
51
+ throw new Error('@owlmeans/test-auth: cannot create TRUSTED record without id');
52
+ }
53
+ if (store.has(record.id)) {
54
+ throw new Error(`@owlmeans/test-auth: TRUSTED record ${record.id} already exists`);
55
+ }
56
+ store.set(record.id, record);
57
+ return record;
58
+ },
59
+ get: () => notImplemented('get'),
60
+ list: () => notImplemented('list'),
61
+ update: () => notImplemented('update'),
62
+ delete: () => notImplemented('delete'),
63
+ pick: () => notImplemented('pick'),
64
+ };
65
+ return appendContextual(alias, resource);
66
+ };
67
+ //# sourceMappingURL=trusted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted.js","sourceRoot":"","sources":["../src/trusted.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@owlmeans/test-auth",
3
+ "version": "0.1.3",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -b",
8
+ "dev": "nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty"
10
+ },
11
+ "main": "build/index.js",
12
+ "module": "build/index.js",
13
+ "types": "build/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./build/index.js",
17
+ "require": "./build/index.js",
18
+ "default": "./build/index.js",
19
+ "module": "./build/index.js",
20
+ "types": "./build/index.d.ts"
21
+ }
22
+ },
23
+ "dependencies": {
24
+ "@owlmeans/auth": "^0.1.3",
25
+ "@owlmeans/auth-common": "^0.1.3",
26
+ "@owlmeans/basic-envelope": "^0.1.3",
27
+ "@owlmeans/basic-keys": "^0.1.3",
28
+ "@owlmeans/context": "^0.1.3",
29
+ "@owlmeans/entrypoint": "^0.1.3",
30
+ "@owlmeans/resource": "^0.1.3",
31
+ "@owlmeans/test": "^0.1.2"
32
+ },
33
+ "devDependencies": {
34
+ "@owlmeans/dep-config": "workspace:*",
35
+ "@types/bun": "^1.3.0",
36
+ "nodemon": "^3.1.11",
37
+ "typescript": "^6.0.2"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }
@@ -0,0 +1,42 @@
1
+ import { EnvelopeKind, makeEnvelopeModel } from '@owlmeans/basic-envelope'
2
+ import type { KeyPairModel } from '@owlmeans/basic-keys'
3
+ import { AuthroizationType } from '@owlmeans/auth'
4
+ import type { Auth } from '@owlmeans/auth'
5
+ import { makeFixtureKeyPair } from './keys.js'
6
+
7
+ const AUTH_BEARER_PREFIX = AuthroizationType.Ed25519BasicToken.toUpperCase()
8
+
9
+ /**
10
+ * Wrap a payload in a signed `EnvelopeModel` using a fixture keypair (or one
11
+ * supplied by the caller) and serialize it. Useful in category-B tests that
12
+ * exercise envelope unwrapping/verification without hitting a real signer.
13
+ */
14
+ export const signMockEnvelope = async <T extends {} | string>(
15
+ msg: T,
16
+ type: string,
17
+ kind: EnvelopeKind = EnvelopeKind.Token,
18
+ kp: KeyPairModel = makeFixtureKeyPair('test-auth-default')
19
+ ): Promise<string> => {
20
+ const envelope = makeEnvelopeModel<T>(type)
21
+ envelope.send(msg)
22
+ const signed = await envelope.sign(kp, kind)
23
+ return signed
24
+ }
25
+
26
+ /**
27
+ * Produce an `Authorization` header value of the form
28
+ * `ED25519-BASIC-TOKEN <encoded>` for a given `Auth`. Lets unit tests of
29
+ * header parsing and guard `match` logic generate valid-looking bearers.
30
+ */
31
+ export const makeBearer = async (
32
+ auth: Auth,
33
+ kp?: KeyPairModel
34
+ ): Promise<string> => {
35
+ const encoded = await signMockEnvelope(
36
+ auth as unknown as Record<string, unknown>,
37
+ AuthroizationType.Ed25519BasicToken,
38
+ EnvelopeKind.Token,
39
+ kp
40
+ )
41
+ return `${AUTH_BEARER_PREFIX} ${encoded}`
42
+ }
@@ -0,0 +1,16 @@
1
+ import { AuthRole, AuthenticationType } from '@owlmeans/auth'
2
+ import type { Auth } from '@owlmeans/auth'
3
+
4
+ const baseAuth = (role: AuthRole, userId: string, token: string): Auth => ({
5
+ type: AuthenticationType.BasicEd25519,
6
+ role,
7
+ userId,
8
+ token,
9
+ isUser: role !== AuthRole.Service && role !== AuthRole.System,
10
+ createdAt: new Date(0),
11
+ scopes: ['*'],
12
+ })
13
+
14
+ export const SUPERUSER: Auth = baseAuth(AuthRole.Superuser, 'superuser-1', 'token-superuser')
15
+ export const USER: Auth = baseAuth(AuthRole.User, 'user-1', 'token-user')
16
+ export const SERVICE: Auth = baseAuth(AuthRole.Service, 'service-1', 'token-service')
package/src/guard.ts ADDED
@@ -0,0 +1,63 @@
1
+ import { createService } from '@owlmeans/context'
2
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
3
+ import { DEFAULT_GUARD } from '@owlmeans/auth-common'
4
+ import type { Auth } from '@owlmeans/auth'
5
+ import type { AbstractRequest, AbstractResponse, GuardService } from '@owlmeans/entrypoint'
6
+
7
+ export interface MockGuardOptions {
8
+ alias?: string
9
+ /** Auth payload that `handle` will resolve into the response. */
10
+ auth?: Auth
11
+ /** Optional predicate that decides whether `match` reports a hit. Defaults to "always match". */
12
+ allow?: (req: AbstractRequest, res: AbstractResponse<unknown>) => boolean | Promise<boolean>
13
+ /** Token returned by the client-side `authenticated()` method. */
14
+ token?: string
15
+ }
16
+
17
+ /**
18
+ * Build a `GuardService` that resolves to a chosen `Auth` without exercising
19
+ * any real cryptography or trusted-record lookup. Use this in category-B
20
+ * tests where the goal is to verify behaviour that depends on an
21
+ * already-authenticated identity.
22
+ *
23
+ * The mock implements the full `GuardService` shape — `match`, `handle`,
24
+ * `authenticated` — so it can be registered on a context exactly the way
25
+ * a real guard is.
26
+ */
27
+ export const makeMockGuard = (opts: MockGuardOptions = {}): GuardService => {
28
+ const alias = opts.alias ?? DEFAULT_GUARD
29
+ const allow = opts.allow ?? (() => true)
30
+
31
+ const guard = createService<GuardService>(alias, {
32
+ token: opts.token,
33
+
34
+ match: async (req, res) => {
35
+ return Boolean(await allow(req, res))
36
+ },
37
+
38
+ handle: async (_req, res) => {
39
+ if (opts.auth != null) {
40
+ res.resolve(opts.auth)
41
+ }
42
+ return opts.auth as never
43
+ },
44
+
45
+ authenticated: async () => opts.token ?? null,
46
+ })
47
+
48
+ return guard
49
+ }
50
+
51
+ /**
52
+ * Register a mock guard on `context` that resolves to the given `auth`.
53
+ * Returns the same context for chaining. Existing guards under the same
54
+ * alias are not replaced — register before any real guard service.
55
+ */
56
+ export const withAuth = <C extends BasicConfig, T extends BasicContext<C>>(
57
+ context: T,
58
+ auth: Auth,
59
+ alias: string = DEFAULT_GUARD
60
+ ): T => {
61
+ const guard = makeMockGuard({ alias, auth })
62
+ return context.registerService<T>(guard)
63
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './keys.js'
2
+ export * from './trusted.js'
3
+ export * from './guard.js'
4
+ export * from './envelope.js'
5
+ export * from './fixtures.js'
package/src/keys.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { makeKeyPairModel } from '@owlmeans/basic-keys'
2
+ import type { KeyPairModel } from '@owlmeans/basic-keys'
3
+ import { createHash } from 'node:crypto'
4
+
5
+ const encodeBase64 = (bytes: Uint8Array): string =>
6
+ Buffer.from(bytes).toString('base64')
7
+
8
+ /**
9
+ * Deterministic Ed25519 keypair derived from `seed`. Same seed → same keys.
10
+ * Use in tests to keep signatures and trust-records stable across runs.
11
+ *
12
+ * Calling without a seed returns a fresh random keypair (re-uses
13
+ * `makeKeyPairModel()` semantics).
14
+ */
15
+ export const makeFixtureKeyPair = (seed?: string): KeyPairModel => {
16
+ if (seed == null) return makeKeyPairModel()
17
+ const primary = createHash('sha256')
18
+ .update(`@owlmeans/test-auth:${seed}`)
19
+ .digest()
20
+ return makeKeyPairModel(`ed25519:${encodeBase64(primary)}`)
21
+ }
package/src/trusted.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { appendContextual } from '@owlmeans/context'
2
+ import type { TrustedRecord } from '@owlmeans/auth-common'
3
+ import type { Resource } from '@owlmeans/resource'
4
+
5
+ const TRUSTED_DEFAULT_ALIAS = 'TRUSTED'
6
+
7
+ const notImplemented = (op: string): never => {
8
+ throw new Error(`@owlmeans/test-auth: in-memory TRUSTED resource does not implement ${op}()`)
9
+ }
10
+
11
+ const getter = (record: TrustedRecord, field: string): unknown =>
12
+ (record as unknown as Record<string, unknown>)[field]
13
+
14
+ /**
15
+ * In-memory `Resource<TrustedRecord>` used to satisfy `trust()` lookups
16
+ * (`@owlmeans/auth-common/utils/trusted.ts`). Pre-populate with the
17
+ * trusted users your code under test expects to find.
18
+ *
19
+ * Only `load`, `save` and `create` are implemented — that covers the
20
+ * `trust()` boundary plus straightforward seeding from tests. Other
21
+ * operations throw, so tests that need them have to be honest about
22
+ * adding integration coverage instead of leaning on the mock.
23
+ */
24
+ export const makeMemoryTrustedResource = (
25
+ records: TrustedRecord[] = [],
26
+ alias: string = TRUSTED_DEFAULT_ALIAS
27
+ ): Resource<TrustedRecord> => {
28
+ const store = new Map<string, TrustedRecord>()
29
+ for (const r of records) {
30
+ if (r.id == null) {
31
+ throw new Error('@owlmeans/test-auth: TRUSTED record without id')
32
+ }
33
+ store.set(r.id, r)
34
+ }
35
+
36
+ const findByField = (value: string, field: string): TrustedRecord | null => {
37
+ for (const record of store.values()) {
38
+ if (getter(record, field) === value) return record
39
+ }
40
+ return null
41
+ }
42
+
43
+ const resource: Partial<Resource<TrustedRecord>> = {
44
+ load: async <Type extends TrustedRecord>(id: string, field?: unknown) => {
45
+ const fieldName = typeof field === 'string'
46
+ ? field
47
+ : (field as { field?: string } | undefined)?.field
48
+ if (fieldName != null && fieldName !== 'id') {
49
+ return (findByField(id, fieldName) as Type | null) ?? null
50
+ }
51
+ return (store.get(id) as Type | undefined) ?? null
52
+ },
53
+
54
+ save: async <Type extends TrustedRecord>(record: Partial<Type>) => {
55
+ if (record.id == null) {
56
+ throw new Error('@owlmeans/test-auth: cannot save TRUSTED record without id')
57
+ }
58
+ store.set(record.id, record as TrustedRecord)
59
+ return record as Type
60
+ },
61
+
62
+ create: async <Type extends TrustedRecord>(record: Partial<Type>) => {
63
+ if (record.id == null) {
64
+ throw new Error('@owlmeans/test-auth: cannot create TRUSTED record without id')
65
+ }
66
+ if (store.has(record.id)) {
67
+ throw new Error(`@owlmeans/test-auth: TRUSTED record ${record.id} already exists`)
68
+ }
69
+ store.set(record.id, record as TrustedRecord)
70
+ return record as Type
71
+ },
72
+
73
+ get: () => notImplemented('get'),
74
+ list: () => notImplemented('list'),
75
+ update: () => notImplemented('update'),
76
+ delete: () => notImplemented('delete'),
77
+ pick: () => notImplemented('pick'),
78
+ }
79
+
80
+ return appendContextual<Resource<TrustedRecord>>(alias, resource)
81
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": [
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.bun.json"
5
+ ],
6
+ "compilerOptions": {
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
+ },
10
+ "exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
11
+ }