@nwire/auth 0.7.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Gefter / 200apps Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @nwire/auth
2
+
3
+ > Identity + authorization contract — `User`, `IdpAdapter`, canonical sign-in/out resolvers.
4
+
5
+ ## What it does
6
+
7
+ Defines the `User` type (with declaration merging for app-specific fields), the `IdpAdapter` interface every authentication backend implements, the `identityPlugin` that wires an adapter into the container, and canonical resolvers (`SignIn`, `SignOut`, `Refresh`, `Register`, `Me`) so apps don't reinvent the boring parts. The `authzMiddleware` reads `action.policy` and a per-request `Authorizer` to enforce access.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @nwire/auth
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { defineApp } from "@nwire/forge";
19
+ import { identityPlugin, authPlugin } from "@nwire/auth";
20
+ import { betterAuthAdapter } from "@nwire/auth-better-auth";
21
+
22
+ defineApp("my-app", {
23
+ plugins: [
24
+ identityPlugin({ adapter: betterAuthAdapter({ auth }) }),
25
+ authPlugin({
26
+ authorizer: {
27
+ authorize: async ({ action, ctx }) => {
28
+ if (action.policy === "admin" && !ctx.envelope.user?.roles?.includes("admin")) {
29
+ throw new ForbiddenError("admin only");
30
+ }
31
+ },
32
+ },
33
+ }),
34
+ ],
35
+ });
36
+ ```
37
+
38
+ ## API surface
39
+
40
+ - `User` — declaration-mergeable identity shape.
41
+ - `IdpAdapter` — interface every auth backend (Logto, better-auth, custom) implements.
42
+ - `identityPlugin({ adapter })` — registers the adapter; wires HTTP middleware to verify tokens.
43
+ - `authPlugin({ authorizer })` / `authzMiddleware({ authorizer })` — enforces `action.policy`.
44
+ - Canonical resolvers: `SignIn`, `SignOut`, `Refresh`, `Register`, `Me`.
45
+ - Error types: `UnauthorizedError`, `ForbiddenError`.
46
+
47
+ ## When to use
48
+
49
+ Whenever you need real users, sessions, or tenant-scoped authorization. Fits L3 and up.
50
+
51
+ ## Standalone use
52
+
53
+ For developers using `@nwire/auth` **without the rest of Nwire** — pair it with any TypeScript project, any container, any HTTP framework.
54
+
55
+ ```ts
56
+ // See the package's main entry (src/) for the standalone surface.
57
+ // The exports below work without @nwire/app or @nwire/forge.
58
+ import {} from /* ...standalone exports... */ "@nwire/auth";
59
+ ```
60
+
61
+ ## Within nwire-app
62
+
63
+ For developers using this package as part of the Nwire stack — register it via `app.use(...)` or it auto-wires when you compose `createApp({ modules })`.
64
+
65
+ ```ts
66
+ import { createApp } from "@nwire/forge";
67
+
68
+ const app = createApp({
69
+ /* ...config... */
70
+ });
71
+ // Adapter/plugin wiring happens here when applicable.
72
+ ```
73
+
74
+ ## See also
75
+
76
+ - [Architecture sketch §05 — Adapters tier](../../architecture-sketch.html#packages)
77
+ - Sibling packages: [@nwire/auth-better-auth](../nwire-auth-better-auth), [@nwire/auth-logto](../nwire-auth-logto), [@nwire/rbac](../nwire-rbac)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * authzMiddleware contract — runs an Authorizer before the handler. Allows
3
+ * by returning, denies by throwing. Optional opt-out for policy-less actions.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * authzMiddleware contract — runs an Authorizer before the handler. Allows
3
+ * by returning, denies by throwing. Optional opt-out for policy-less actions.
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { z } from "zod";
7
+ import { defineAction, defineHandler, Runtime } from "@nwire/forge";
8
+ import { seedEnvelope } from "@nwire/envelope";
9
+ import { authzMiddleware, UnauthorizedError, ForbiddenError } from "../auth.js";
10
+ describe("authzMiddleware", () => {
11
+ it("runs the handler when the authorizer returns", async () => {
12
+ const action = defineAction({ name: "auth.ok", schema: z.object({}) });
13
+ let ran = false;
14
+ const handler = defineHandler(action, async () => {
15
+ ran = true;
16
+ return undefined;
17
+ });
18
+ const allowAll = { async authorize() { } };
19
+ const runtime = new Runtime();
20
+ runtime.registerHandler(handler);
21
+ runtime.use(authzMiddleware({ authorizer: allowAll }));
22
+ await runtime.dispatch(action, {});
23
+ expect(ran).toBe(true);
24
+ });
25
+ it("blocks the handler when the authorizer throws", async () => {
26
+ const action = defineAction({ name: "auth.deny", schema: z.object({}) });
27
+ let ran = false;
28
+ const handler = defineHandler(action, async () => {
29
+ ran = true;
30
+ return undefined;
31
+ });
32
+ const denyAll = {
33
+ async authorize() {
34
+ throw new UnauthorizedError("no token");
35
+ },
36
+ };
37
+ const runtime = new Runtime();
38
+ runtime.registerHandler(handler);
39
+ runtime.use(authzMiddleware({ authorizer: denyAll }));
40
+ await expect(runtime.dispatch(action, {})).rejects.toThrow(UnauthorizedError);
41
+ expect(ran).toBe(false);
42
+ });
43
+ it("reads action.policy and ctx.envelope.userId to decide", async () => {
44
+ const adminOnly = defineAction({
45
+ name: "auth.admin",
46
+ schema: z.object({}),
47
+ policy: "admin-only",
48
+ });
49
+ const handler = defineHandler(adminOnly, async () => undefined);
50
+ const adminAuthorizer = {
51
+ async authorize(action, ctx) {
52
+ if (action.policy === "admin-only" && ctx.envelope.userId !== "miri") {
53
+ throw new ForbiddenError("admins only");
54
+ }
55
+ },
56
+ };
57
+ const runtime = new Runtime();
58
+ runtime.registerHandler(handler);
59
+ runtime.use(authzMiddleware({ authorizer: adminAuthorizer }));
60
+ // Wrong user → forbidden
61
+ await expect(runtime.dispatch(adminOnly, {}, seedEnvelope({ userId: "avi" }))).rejects.toThrow(ForbiddenError);
62
+ // Right user → allowed
63
+ await runtime.dispatch(adminOnly, {}, seedEnvelope({ userId: "miri" }));
64
+ });
65
+ it("with enforceAll: false, skips actions that have no policy", async () => {
66
+ const publicAction = defineAction({ name: "auth.public", schema: z.object({}) });
67
+ const guardedAction = defineAction({
68
+ name: "auth.guarded",
69
+ schema: z.object({}),
70
+ policy: "login-required",
71
+ });
72
+ const handlerA = defineHandler(publicAction, async () => undefined);
73
+ const handlerB = defineHandler(guardedAction, async () => undefined);
74
+ let authorizerCalls = 0;
75
+ const authorizer = {
76
+ async authorize() {
77
+ authorizerCalls++;
78
+ throw new UnauthorizedError();
79
+ },
80
+ };
81
+ const runtime = new Runtime();
82
+ runtime.registerHandler(handlerA);
83
+ runtime.registerHandler(handlerB);
84
+ runtime.use(authzMiddleware({ authorizer, enforceAll: false }));
85
+ await runtime.dispatch(publicAction, {}); // skipped
86
+ expect(authorizerCalls).toBe(0);
87
+ await expect(runtime.dispatch(guardedAction, {})).rejects.toThrow(UnauthorizedError);
88
+ expect(authorizerCalls).toBe(1);
89
+ });
90
+ });
91
+ //# sourceMappingURL=auth.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Identity contract — proves canonical resolvers delegate to whatever
3
+ * IdpAdapter is registered, and that the identityPlugin wires the adapter
4
+ * onto the container in time for resolvers to use it.
5
+ *
6
+ * We don't ship a real adapter at this layer (those live in
7
+ * @nwire/auth-better-auth / @nwire/auth-logto / @nwire/auth-passport).
8
+ * The test uses a hand-rolled fake adapter that captures calls so we can
9
+ * assert routing without depending on an external IdP.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=identity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/identity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Identity contract — proves canonical resolvers delegate to whatever
3
+ * IdpAdapter is registered, and that the identityPlugin wires the adapter
4
+ * onto the container in time for resolvers to use it.
5
+ *
6
+ * We don't ship a real adapter at this layer (those live in
7
+ * @nwire/auth-better-auth / @nwire/auth-logto / @nwire/auth-passport).
8
+ * The test uses a hand-rolled fake adapter that captures calls so we can
9
+ * assert routing without depending on an external IdP.
10
+ */
11
+ import { describe, it, expect } from "vitest";
12
+ import { createApp, defineModule } from "@nwire/forge";
13
+ import { identityPlugin, SignIn, SignOut, Refresh, Register, Me, } from "../auth.js";
14
+ function fakeAdapter() {
15
+ const calls = [];
16
+ const users = new Map();
17
+ return {
18
+ calls,
19
+ users,
20
+ async verifyToken(token) {
21
+ calls.push({ name: "verifyToken", args: [token] });
22
+ return users.get(token) ?? null;
23
+ },
24
+ async signIn(input) {
25
+ calls.push({ name: "signIn", args: [input] });
26
+ const user = { id: "u_1", email: input.email ?? "x@y.z" };
27
+ users.set("tok-1", user);
28
+ return { user, tokens: { accessToken: "tok-1", refreshToken: "ref-1" } };
29
+ },
30
+ async signOut(token) {
31
+ calls.push({ name: "signOut", args: [token] });
32
+ users.delete(token);
33
+ },
34
+ async refresh(refreshToken) {
35
+ calls.push({ name: "refresh", args: [refreshToken] });
36
+ return { accessToken: "tok-2" };
37
+ },
38
+ async register(input) {
39
+ calls.push({ name: "register", args: [input] });
40
+ const user = { id: "u_new", email: input.email };
41
+ return { user, tokens: { accessToken: "tok-new" } };
42
+ },
43
+ };
44
+ }
45
+ const empty = defineModule("empty", {});
46
+ describe("identityPlugin + canonical resolvers", () => {
47
+ it("registers the adapter on the container under 'idp'", async () => {
48
+ const adapter = fakeAdapter();
49
+ const app = createApp({
50
+ modules: [empty],
51
+ plugins: [identityPlugin({ adapter })],
52
+ });
53
+ await app.start();
54
+ const resolved = app.runtime.getContainer().resolve("idp");
55
+ expect(resolved).toBe(adapter);
56
+ await app.stop();
57
+ });
58
+ it("SignIn delegates to adapter.signIn and returns user + tokens", async () => {
59
+ const adapter = fakeAdapter();
60
+ const app = createApp({
61
+ modules: [empty],
62
+ plugins: [identityPlugin({ adapter })],
63
+ });
64
+ await app.start();
65
+ const result = await app.callResolver(SignIn, {
66
+ input: { email: "alice@example.com", password: "secret123" },
67
+ });
68
+ expect(adapter.calls[0]?.name).toBe("signIn");
69
+ expect(result.body.user.id).toBe("u_1");
70
+ expect(result.body.tokens.accessToken).toBe("tok-1");
71
+ await app.stop();
72
+ });
73
+ it("SignOut + verify roundtrip — Me returns user before sign-out, NotFound after", async () => {
74
+ const adapter = fakeAdapter();
75
+ const app = createApp({
76
+ modules: [empty],
77
+ plugins: [identityPlugin({ adapter })],
78
+ });
79
+ await app.start();
80
+ await app.callResolver(SignIn, {
81
+ input: { email: "alice@example.com", password: "secret123" },
82
+ });
83
+ const me1 = await app.callResolver(Me, { input: { token: "tok-1" } });
84
+ expect(me1.body.id).toBe("u_1");
85
+ await app.callResolver(SignOut, { input: { token: "tok-1" } });
86
+ await expect(app.callResolver(Me, { input: { token: "tok-1" } })).rejects.toThrow();
87
+ await app.stop();
88
+ });
89
+ it("Refresh delegates when adapter supports it", async () => {
90
+ const adapter = fakeAdapter();
91
+ const app = createApp({
92
+ modules: [empty],
93
+ plugins: [identityPlugin({ adapter })],
94
+ });
95
+ await app.start();
96
+ const result = await app.callResolver(Refresh, { input: { refreshToken: "ref-1" } });
97
+ expect(result.body.accessToken).toBe("tok-2");
98
+ await app.stop();
99
+ });
100
+ it("Register returns user + initial tokens", async () => {
101
+ const adapter = fakeAdapter();
102
+ const app = createApp({
103
+ modules: [empty],
104
+ plugins: [identityPlugin({ adapter })],
105
+ });
106
+ await app.start();
107
+ const result = await app.callResolver(Register, {
108
+ input: { email: "new@example.com", password: "long-enough-password" },
109
+ });
110
+ expect(result.body.user.email).toBe("new@example.com");
111
+ expect(result.body.tokens.accessToken).toBe("tok-new");
112
+ await app.stop();
113
+ });
114
+ it("Refresh throws NotFound when adapter omits refresh()", async () => {
115
+ const adapter = fakeAdapter();
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ delete adapter.refresh;
118
+ const app = createApp({
119
+ modules: [empty],
120
+ plugins: [identityPlugin({ adapter })],
121
+ });
122
+ await app.start();
123
+ await expect(app.callResolver(Refresh, { input: { refreshToken: "x" } })).rejects.toThrow();
124
+ await app.stop();
125
+ });
126
+ it("adapter.shutdown is called on app.stop()", async () => {
127
+ let shutdownCalled = false;
128
+ const adapter = {
129
+ ...fakeAdapter(),
130
+ shutdown: async () => { shutdownCalled = true; },
131
+ };
132
+ const app = createApp({
133
+ modules: [empty],
134
+ plugins: [identityPlugin({ adapter })],
135
+ });
136
+ await app.start();
137
+ await app.stop();
138
+ expect(shutdownCalled).toBe(true);
139
+ });
140
+ });
141
+ //# sourceMappingURL=identity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.test.js","sourceRoot":"","sources":["../../src/__tests__/identity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EACL,cAAc,EACd,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,EAAE,GAGH,MAAM,SAAS,CAAC;AAEjB,SAAS,WAAW;IAIlB,MAAM,KAAK,GAAwC,EAAE,CAAC;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IACtC,OAAO;QACL,KAAK;QACL,KAAK;QACL,KAAK,CAAC,WAAW,CAAC,KAAK;YACrB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnD,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAS,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,OAAO,EAAE,CAAC;YAChE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,CAAC;QAC3E,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,KAAK;YACjB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC/C,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,YAAY;YACxB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YACtD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK;YAClB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAChD,MAAM,IAAI,GAAS,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,CAAC;QACtD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAExC,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAElB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE;YAC5C,KAAK,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE;SAC7D,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,KAAK,IAAI,EAAE;QAC5F,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAElB,MAAM,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE;YAC7B,KAAK,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,WAAW,EAAE;SAC7D,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEhC,MAAM,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAE/D,MAAM,MAAM,CACV,GAAG,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CACpD,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAElB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QACrF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAElB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE;YAC9C,KAAK,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,sBAAsB,EAAE;SACtE,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;QAC9B,8DAA8D;QAC9D,OAAQ,OAAe,CAAC,OAAO,CAAC;QAEhC,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,MAAM,CACV,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5D,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,IAAI,cAAc,GAAG,KAAK,CAAC;QAC3B,MAAM,OAAO,GAAe;YAC1B,GAAG,WAAW,EAAE;YAChB,QAAQ,EAAE,KAAK,IAAI,EAAE,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC;SACjD,CAAC;QACF,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,KAAK,CAAC;YAChB,OAAO,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * End-to-end test for the new `{name}Route` + `{name}Handler` shape that
3
+ * supersedes the resolver-based exports. Boots a real http.Server, mounts
4
+ * every canonical auth route via `mountAuth(api)`, and exercises sign-in
5
+ * → me → sign-out via fetch.
6
+ *
7
+ * The fake adapter is the same one identity.test.ts uses — exercises the
8
+ * full path: routing → middleware → schema validation → container resolve
9
+ * → adapter call → resource projection → response.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=routes.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/routes.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * End-to-end test for the new `{name}Route` + `{name}Handler` shape that
3
+ * supersedes the resolver-based exports. Boots a real http.Server, mounts
4
+ * every canonical auth route via `mountAuth(api)`, and exercises sign-in
5
+ * → me → sign-out via fetch.
6
+ *
7
+ * The fake adapter is the same one identity.test.ts uses — exercises the
8
+ * full path: routing → middleware → schema validation → container resolve
9
+ * → adapter call → resource projection → response.
10
+ */
11
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
12
+ import http from "node:http";
13
+ import { createApp, defineModule } from "@nwire/forge";
14
+ import { httpInterface } from "@nwire/http";
15
+ import { identityPlugin, mountAuth } from "../auth.js";
16
+ function fakeAdapter() {
17
+ const users = new Map();
18
+ return {
19
+ users,
20
+ async verifyToken(token) {
21
+ return users.get(token) ?? null;
22
+ },
23
+ async signIn(input) {
24
+ const user = { id: "u_1", email: input.email ?? "x@y.z" };
25
+ users.set("tok-1", user);
26
+ return { user, tokens: { accessToken: "tok-1", refreshToken: "ref-1" } };
27
+ },
28
+ async signOut(token) {
29
+ users.delete(token);
30
+ },
31
+ };
32
+ }
33
+ const empty = defineModule("empty", {});
34
+ let server;
35
+ let baseUrl;
36
+ let adapter;
37
+ beforeAll(async () => {
38
+ adapter = fakeAdapter();
39
+ const app = createApp({
40
+ modules: [empty],
41
+ plugins: [identityPlugin({ adapter })],
42
+ });
43
+ await app.start();
44
+ const api = mountAuth(httpInterface({ prefix: "/api/v1" }).provide(app.runtime.getContainer()));
45
+ server = http.createServer(api.compile());
46
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
47
+ const addr = server.address();
48
+ baseUrl = `http://127.0.0.1:${addr.port}/api/v1`;
49
+ });
50
+ afterAll(async () => {
51
+ await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
52
+ });
53
+ async function req(method, path, body) {
54
+ const res = await fetch(`${baseUrl}${path}`, {
55
+ method,
56
+ headers: { "content-type": "application/json" },
57
+ body: body !== undefined ? JSON.stringify(body) : undefined,
58
+ });
59
+ const text = await res.text();
60
+ let parsed = text;
61
+ try {
62
+ parsed = text ? JSON.parse(text) : undefined;
63
+ }
64
+ catch {
65
+ /* leave as text */
66
+ }
67
+ return { status: res.status, body: parsed };
68
+ }
69
+ describe("mountAuth — canonical auth routes over real HTTP", () => {
70
+ it("POST /auth/sign-in returns the user + tokens", async () => {
71
+ const res = await req("POST", "/auth/sign-in", {
72
+ email: "alice@example.com",
73
+ password: "secret123",
74
+ });
75
+ expect(res.status).toBe(200);
76
+ expect(res.body.user.id).toBe("u_1");
77
+ expect(res.body.tokens.accessToken).toBe("tok-1");
78
+ });
79
+ it("POST /auth/sign-in with bad body returns 400", async () => {
80
+ const res = await req("POST", "/auth/sign-in", { email: "not-an-email", password: "x" });
81
+ expect(res.status).toBe(400);
82
+ expect(res.body.error.code).toBe("validation_failed");
83
+ });
84
+ it("POST /auth/sign-out + GET /auth/me — token invalidates", async () => {
85
+ // Make sure we have a valid token first
86
+ await req("POST", "/auth/sign-in", { email: "alice@example.com", password: "secret123" });
87
+ const me1 = await req("POST", "/auth/me", { token: "tok-1" });
88
+ // NOTE: meRoute is GET in the spec but takes a token in body; fetch
89
+ // with GET + body is non-standard. The route definition uses GET +
90
+ // body, which Koa accepts. Use POST for the test to keep it portable.
91
+ // (We test the route's URL not the verb here; verb portability is
92
+ // a separate concern handled in G5b-3 cleanup.)
93
+ void me1;
94
+ const so = await req("POST", "/auth/sign-out", { token: "tok-1" });
95
+ expect(so.status).toBe(202);
96
+ expect(adapter.users.has("tok-1")).toBe(false);
97
+ });
98
+ });
99
+ //# sourceMappingURL=routes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.test.js","sourceRoot":"","sources":["../../src/__tests__/routes.test.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `@nwire/auth` — identity + authorization contract.
3
+ *
4
+ * Plugin form (`authPlugin`) or middleware factory (`authzMiddleware`):
5
+ * both read `action.policy` and per-request state from `ctx.envelope` /
6
+ * container, delegating the decision to the consumer-provided
7
+ * `Authorizer`. The framework does not impose a policy model.
8
+ *
9
+ * See: architecture-sketch.html §05 (Adapters tier).
10
+ */
11
+ import type { ActionDefinition, DispatchMiddleware, HandlerContext, PluginDefinition } from "@nwire/forge";
12
+ export interface Authorizer {
13
+ /**
14
+ * Decide whether the current envelope/user may invoke `action`.
15
+ * Throw to deny (typically with `UnauthorizedError` or `ForbiddenError`);
16
+ * return (or resolve `void`) to allow.
17
+ */
18
+ authorize(action: ActionDefinition, ctx: HandlerContext): Promise<void> | void;
19
+ }
20
+ export declare class UnauthorizedError extends Error {
21
+ readonly $kind: "auth.unauthorized";
22
+ constructor(message?: string);
23
+ }
24
+ export declare class ForbiddenError extends Error {
25
+ readonly $kind: "auth.forbidden";
26
+ constructor(message?: string);
27
+ }
28
+ export interface AuthzMiddlewareOptions {
29
+ readonly authorizer: Authorizer;
30
+ /**
31
+ * If `true` (default), every action passes through the authorizer.
32
+ * If `false`, only actions with a `policy` field are checked — anonymous
33
+ * actions get a free pass. Useful in transitional codebases.
34
+ */
35
+ readonly enforceAll?: boolean;
36
+ }
37
+ export declare function authzMiddleware(options: AuthzMiddlewareOptions): DispatchMiddleware;
38
+ /**
39
+ * Plugin form — the recommended shape for new code.
40
+ * createApp({ plugins: [authPlugin({ authorizer })] })
41
+ */
42
+ export declare function authPlugin(options: AuthzMiddlewareOptions): PluginDefinition;
43
+ export type { User } from "./user.js";
44
+ export { identityPlugin, type IdpAdapter, type IdentityPluginOptions, type SignInInput, type RegisterInput, type AuthTokens, } from "./identity.js";
45
+ export { mountAuth, signInRoute, signInHandler, signOutRoute, signOutHandler, refreshRoute, refreshHandler, registerRoute, registerHandler, requestPasswordResetRoute, requestPasswordResetHandler, resetPasswordRoute, resetPasswordHandler, verifyEmailRoute, verifyEmailHandler, meRoute, meHandler, type MountAuthOptions, } from "./routes.js";
46
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EACjB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAChF;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,KAAK,EAAG,mBAAmB,CAAU;gBAClC,OAAO,SAAiB;CAIrC;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,KAAK,EAAG,gBAAgB,CAAU;gBAC/B,OAAO,SAAc;CAIlC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,kBAAkB,CASnF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,sBAAsB,GAAG,gBAAgB,CAI5E;AAMD,YAAY,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EACL,cAAc,EACd,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,yBAAyB,EACzB,2BAA2B,EAC3B,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,SAAS,EACT,KAAK,gBAAgB,GACtB,MAAM,UAAU,CAAC"}
package/dist/auth.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `@nwire/auth` — identity + authorization contract.
3
+ *
4
+ * Plugin form (`authPlugin`) or middleware factory (`authzMiddleware`):
5
+ * both read `action.policy` and per-request state from `ctx.envelope` /
6
+ * container, delegating the decision to the consumer-provided
7
+ * `Authorizer`. The framework does not impose a policy model.
8
+ *
9
+ * See: architecture-sketch.html §05 (Adapters tier).
10
+ */
11
+ import { definePlugin } from "@nwire/forge";
12
+ export class UnauthorizedError extends Error {
13
+ $kind = "auth.unauthorized";
14
+ constructor(message = "unauthorized") {
15
+ super(message);
16
+ this.name = "UnauthorizedError";
17
+ }
18
+ }
19
+ export class ForbiddenError extends Error {
20
+ $kind = "auth.forbidden";
21
+ constructor(message = "forbidden") {
22
+ super(message);
23
+ this.name = "ForbiddenError";
24
+ }
25
+ }
26
+ export function authzMiddleware(options) {
27
+ const enforceAll = options.enforceAll ?? true;
28
+ return async (next, action, _input, ctx) => {
29
+ if (!enforceAll && action.policy === undefined) {
30
+ return next();
31
+ }
32
+ await options.authorizer.authorize(action, ctx);
33
+ return next();
34
+ };
35
+ }
36
+ /**
37
+ * Plugin form — the recommended shape for new code.
38
+ * createApp({ plugins: [authPlugin({ authorizer })] })
39
+ */
40
+ export function authPlugin(options) {
41
+ return definePlugin("auth", {
42
+ middleware: [authzMiddleware(options)],
43
+ });
44
+ }
45
+ export { identityPlugin, } from "./identity.js";
46
+ // ─── HTTP routes — canonical auth surface ──────────────────────────
47
+ // `mountAuth(api)` wires every canonical auth operation onto an
48
+ // httpInterface in one call; individual `{name}Route` + `{name}Handler`
49
+ // pairs are exported for apps that wire by hand.
50
+ export { mountAuth, signInRoute, signInHandler, signOutRoute, signOutHandler, refreshRoute, refreshHandler, registerRoute, registerHandler, requestPasswordResetRoute, requestPasswordResetHandler, resetPasswordRoute, resetPasswordHandler, verifyEmailRoute, verifyEmailHandler, meRoute, meHandler, } from "./routes.js";
51
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAW5C,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,KAAK,GAAG,mBAA4B,CAAC;IAC9C,YAAY,OAAO,GAAG,cAAc;QAClC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,KAAK,GAAG,gBAAyB,CAAC;IAC3C,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAYD,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC;IAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;QACzC,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/C,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChD,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAA+B;IACxD,OAAO,YAAY,CAAC,MAAM,EAAE;QAC1B,UAAU,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;KACvC,CAAC,CAAC;AACL,CAAC;AAOD,OAAO,EACL,cAAc,GAMf,MAAM,YAAY,CAAC;AACpB,sEAAsE;AACtE,gEAAgE;AAChE,wEAAwE;AACxE,iDAAiD;AACjD,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,yBAAyB,EACzB,2BAA2B,EAC3B,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,SAAS,GAEV,MAAM,UAAU,CAAC"}