@daltonr/authwrite-express 0.1.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.
@@ -0,0 +1,24 @@
1
+ import type { RequestHandler, Request, Response } from 'express';
2
+ import type { AuthEvaluator, Decision, Subject, Resource } from '@daltonr/authwrite-core';
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ authDecision?: Decision;
7
+ }
8
+ }
9
+ }
10
+ export interface AuthMiddlewareConfig<S extends Subject = Subject, R extends Resource = Resource> {
11
+ engine: AuthEvaluator<S, R>;
12
+ subject: (req: Request) => S | Promise<S>;
13
+ resource?: (req: Request) => R | undefined | Promise<R | undefined>;
14
+ action: string | ((req: Request) => string);
15
+ onDeny?: (req: Request, res: Response, decision: Decision) => void | Promise<void>;
16
+ }
17
+ export declare function createAuthMiddleware<S extends Subject = Subject, R extends Resource = Resource>(config: AuthMiddlewareConfig<S, R>): RequestHandler;
18
+ export interface ExpressAuthConfig<S extends Subject = Subject, R extends Resource = Resource> {
19
+ engine: AuthEvaluator<S, R>;
20
+ subject: (req: Request) => S | Promise<S>;
21
+ resource?: (req: Request) => R | undefined | Promise<R | undefined>;
22
+ onDeny?: (req: Request, res: Response, decision: Decision) => void | Promise<void>;
23
+ }
24
+ export declare function createExpressAuth<S extends Subject = Subject, R extends Resource = Resource>(base: ExpressAuthConfig<S, R>): (action: string | ((req: Request) => string), overrides?: Partial<ExpressAuthConfig<S, R>>) => RequestHandler;
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ // ─── Middleware factory ───────────────────────────────────────────────────────
2
+ export function createAuthMiddleware(config) {
3
+ return async (req, res, next) => {
4
+ try {
5
+ const subject = await config.subject(req);
6
+ const resource = config.resource ? await config.resource(req) : undefined;
7
+ const action = typeof config.action === 'function' ? config.action(req) : config.action;
8
+ const decision = await config.engine.evaluate({ subject, resource, action });
9
+ req.authDecision = decision;
10
+ if (decision.allowed) {
11
+ next();
12
+ return;
13
+ }
14
+ if (config.onDeny) {
15
+ await config.onDeny(req, res, decision);
16
+ return;
17
+ }
18
+ res.status(403).json({ error: 'forbidden', reason: decision.reason });
19
+ }
20
+ catch (err) {
21
+ next(err);
22
+ }
23
+ };
24
+ }
25
+ export function createExpressAuth(base) {
26
+ return function auth(action, overrides) {
27
+ return createAuthMiddleware({ ...base, ...overrides, action });
28
+ };
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA0BA,iFAAiF;AAEjF,MAAM,UAAU,oBAAoB,CAGlC,MAAkC;IAClC,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,CAAC;YACH,MAAM,OAAO,GAAI,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YACzE,MAAM,MAAM,GAAK,OAAO,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAA;YAEzF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAE3E;YAAC,GAA4C,CAAC,YAAY,GAAG,QAAQ,CAAA;YAEtE,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,IAAI,EAAE,CAAA;gBACN,OAAM;YACR,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;gBACvC,OAAM;YACR,CAAC;YAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,CAAA;QACX,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AA+BD,MAAM,UAAU,iBAAiB,CAG/B,IAA6B;IAC7B,OAAO,SAAS,IAAI,CAClB,MAA2C,EAC3C,SAA4C;QAE5C,OAAO,oBAAoB,CAAO,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;IACtE,CAAC,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@daltonr/authwrite-express",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Express middleware adapter for AuthEngine.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/richardadalton/authwrite.git",
10
+ "directory": "packages/express"
11
+ },
12
+ "keywords": ["authorization", "authz", "express", "middleware"],
13
+ "sideEffects": false,
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "files": ["dist", "src", "README.md", "LICENSE"],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
26
+ "prepublishOnly": "test -d dist && echo 'dist already built, skipping' || (npm run clean && npm run build)"
27
+ },
28
+ "dependencies": {
29
+ "@daltonr/authwrite-core": "*"
30
+ },
31
+ "peerDependencies": {
32
+ "express": ">=4.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/express": "^5.0.0",
36
+ "express": "^5.0.0"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { RequestHandler, Request, Response } from 'express'
2
+ import type { AuthEvaluator, Decision, Subject, Resource } from '@daltonr/authwrite-core'
3
+
4
+ // ─── Request augmentation ─────────────────────────────────────────────────────
5
+
6
+ declare global {
7
+ namespace Express {
8
+ interface Request {
9
+ authDecision?: Decision
10
+ }
11
+ }
12
+ }
13
+
14
+ // ─── Per-middleware config ────────────────────────────────────────────────────
15
+
16
+ export interface AuthMiddlewareConfig<
17
+ S extends Subject = Subject,
18
+ R extends Resource = Resource,
19
+ > {
20
+ engine: AuthEvaluator<S, R>
21
+ subject: (req: Request) => S | Promise<S>
22
+ resource?: (req: Request) => R | undefined | Promise<R | undefined>
23
+ action: string | ((req: Request) => string)
24
+ onDeny?: (req: Request, res: Response, decision: Decision) => void | Promise<void>
25
+ }
26
+
27
+ // ─── Middleware factory ───────────────────────────────────────────────────────
28
+
29
+ export function createAuthMiddleware<
30
+ S extends Subject = Subject,
31
+ R extends Resource = Resource,
32
+ >(config: AuthMiddlewareConfig<S, R>): RequestHandler {
33
+ return async (req, res, next) => {
34
+ try {
35
+ const subject = await config.subject(req)
36
+ const resource = config.resource ? await config.resource(req) : undefined
37
+ const action = typeof config.action === 'function' ? config.action(req) : config.action
38
+
39
+ const decision = await config.engine.evaluate({ subject, resource, action })
40
+
41
+ ;(req as Request & { authDecision: Decision }).authDecision = decision
42
+
43
+ if (decision.allowed) {
44
+ next()
45
+ return
46
+ }
47
+
48
+ if (config.onDeny) {
49
+ await config.onDeny(req, res, decision)
50
+ return
51
+ }
52
+
53
+ res.status(403).json({ error: 'forbidden', reason: decision.reason })
54
+ } catch (err) {
55
+ next(err)
56
+ }
57
+ }
58
+ }
59
+
60
+ // ─── Bound auth factory ───────────────────────────────────────────────────────
61
+ //
62
+ // Captures the engine, subject resolver, resource resolver, and default onDeny
63
+ // once. Returns a function that produces middleware for a specific action.
64
+ // Eliminates the wrapper function pattern every integration reinvents:
65
+ //
66
+ // // Before:
67
+ // function authFor(action: string) {
68
+ // return createAuthMiddleware({ engine, subject: getUser, resource: getDoc, action, onDeny })
69
+ // }
70
+ //
71
+ // // After:
72
+ // const auth = createExpressAuth({ engine, subject: getUser, resource: getDoc, onDeny })
73
+ // app.get('/docs/:id', auth('read'), handler)
74
+ // app.post('/docs/:id', auth('write'), handler)
75
+ //
76
+ // Per-route overrides are supported:
77
+ // app.get('/docs/:id/admin', auth('admin-view', { resource: () => undefined }), handler)
78
+
79
+ export interface ExpressAuthConfig<
80
+ S extends Subject = Subject,
81
+ R extends Resource = Resource,
82
+ > {
83
+ engine: AuthEvaluator<S, R>
84
+ subject: (req: Request) => S | Promise<S>
85
+ resource?: (req: Request) => R | undefined | Promise<R | undefined>
86
+ onDeny?: (req: Request, res: Response, decision: Decision) => void | Promise<void>
87
+ }
88
+
89
+ export function createExpressAuth<
90
+ S extends Subject = Subject,
91
+ R extends Resource = Resource,
92
+ >(base: ExpressAuthConfig<S, R>) {
93
+ return function auth(
94
+ action: string | ((req: Request) => string),
95
+ overrides?: Partial<ExpressAuthConfig<S, R>>,
96
+ ): RequestHandler {
97
+ return createAuthMiddleware<S, R>({ ...base, ...overrides, action })
98
+ }
99
+ }