@occupop/lib-auth 1.0.3 → 4.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.
- package/README.md +42 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +31 -6
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -27,6 +27,20 @@ ENV variables required
|
|
|
27
27
|
JWT_SECRET=randomTokenProvidedByAuthService
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
## Compatibility
|
|
31
|
+
|
|
32
|
+
- **Node:** an ES module package (`"type": "module"`), loadable with `import` and with `require()`.
|
|
33
|
+
`require()` of an ES module needs `require(esm)`, which is on by default since Node 20.19 and
|
|
34
|
+
22.12; Node 22 and 24 are the versions tested. Since 4.1.0 the package takes `jsonwebtoken`, a
|
|
35
|
+
CommonJS module, through its default export, so it loads in Node. 4.0.0 and earlier loaded only
|
|
36
|
+
under Bun: Node failed with "Named export 'verify' not found".
|
|
37
|
+
- **Bun:** loads with `import`, as before.
|
|
38
|
+
- **Peers:** `@occupop/lib-container` `^4.0.1`, the first 4.x version that re-exports awilix's
|
|
39
|
+
`asValue`, which this package imports; `typescript` `^5.6.3 || ^6.0.0`.
|
|
40
|
+
|
|
41
|
+
`bun run dist` ends with `bun run test:node`, which loads the built `dist/` with Node through both
|
|
42
|
+
`import()` and `require()`.
|
|
43
|
+
|
|
30
44
|
## Register
|
|
31
45
|
|
|
32
46
|
```typescript
|
|
@@ -118,6 +132,11 @@ await httpServer.listen(httpPort, () => {
|
|
|
118
132
|
// ...
|
|
119
133
|
```
|
|
120
134
|
|
|
135
|
+
The middleware attaches the request-scoped container as `req.container`, then calls `next()`. If
|
|
136
|
+
building the context fails (for example, a request carries a token while `JWT_SECRET` is not set),
|
|
137
|
+
it calls `next(error)` instead, and Express's error handling takes over. Before 4.1.0 it never
|
|
138
|
+
called `next()`, so a request passing through it hung.
|
|
139
|
+
|
|
121
140
|
```typescript
|
|
122
141
|
// app/router.ts
|
|
123
142
|
export function makeAppRouter({ httpRouter }: Deps) {
|
|
@@ -144,5 +163,28 @@ export function makeGetJobsUseCase({ authUser, jobService }: Deps) {
|
|
|
144
163
|
}
|
|
145
164
|
```
|
|
146
165
|
|
|
166
|
+
## Signing a token for another service
|
|
167
|
+
|
|
168
|
+
`signAuthUserToken(authUser)` signs a user as a bearer token that another service verifies with
|
|
169
|
+
the same `JWT_SECRET`, for example to call the GraphQL gateway on that user's behalf:
|
|
147
170
|
|
|
171
|
+
```typescript
|
|
172
|
+
import { signAuthUserToken } from '@occupop/lib-auth'
|
|
173
|
+
|
|
174
|
+
const token = signAuthUserToken(authUser) // a string, or null when authUser is null
|
|
175
|
+
const response = await fetch(gatewayUrl, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
|
|
178
|
+
body: JSON.stringify({ query }),
|
|
179
|
+
})
|
|
180
|
+
```
|
|
148
181
|
|
|
182
|
+
- HS256 with `JWT_SECRET`, read when the function is called. If it is not set, the function throws
|
|
183
|
+
`'Env var JWT_SECRET not set?'`, as verification does.
|
|
184
|
+
- The payload is signed as given. A stored user that carries `iat` and `exp` keeps both; when it
|
|
185
|
+
has no `iat`, jsonwebtoken adds the signing time. No expiry, issuer or audience is added, so a
|
|
186
|
+
user without `exp` gives a token that does not expire.
|
|
187
|
+
- `null` in, `null` out.
|
|
188
|
+
- The token is identical to the one `@occupop/lib-context` 2.0.1's `makeGetAuthUserToken`
|
|
189
|
+
produces for the same user, secret and time. `src/index_test.ts` checks this against tokens
|
|
190
|
+
recorded from lib-context (`src/auth-user-token-vectors.json`).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IncomingMessage } from 'node:http';
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { type AwilixContainer } from '@occupop/lib-container';
|
|
3
3
|
type Request = IncomingMessage & {
|
|
4
4
|
container?: AwilixContainer;
|
|
@@ -19,6 +19,13 @@ export type ScopedUser = {
|
|
|
19
19
|
};
|
|
20
20
|
export declare function getAuthUser(req: Request): AuthUser | null;
|
|
21
21
|
export declare function getScopedUser(req: Request): ScopedUser | null;
|
|
22
|
+
/**
|
|
23
|
+
* Signs `authUser` with `JWT_SECRET` (HS256), for calling another service on the user's behalf.
|
|
24
|
+
* The payload is signed as given: an `iat` or `exp` it carries is kept, jsonwebtoken adds `iat`
|
|
25
|
+
* only when it has none, and no expiry, issuer or audience is added. The token is the one
|
|
26
|
+
* `@occupop/lib-context` 2.0.1's `makeGetAuthUserToken` produces. Returns `null` for no user.
|
|
27
|
+
*/
|
|
28
|
+
export declare function signAuthUserToken(authUser: AuthUser | null): string | null;
|
|
22
29
|
export declare function makeAuthContextValueFunction<IContainer extends AwilixContainer>({ container }: {
|
|
23
30
|
container: IContainer;
|
|
24
31
|
}): ({ req }: {
|
|
@@ -26,7 +33,11 @@ export declare function makeAuthContextValueFunction<IContainer extends AwilixCo
|
|
|
26
33
|
}) => Promise<{
|
|
27
34
|
container: AwilixContainer<any>;
|
|
28
35
|
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Express middleware: attaches the request-scoped container as `req.container`, then calls
|
|
38
|
+
* `next()`. If building the context throws, it calls `next(error)` instead.
|
|
39
|
+
*/
|
|
29
40
|
export declare function makeAuthContextMiddleware<IContainer extends AwilixContainer>({ container, }: {
|
|
30
41
|
container: IContainer;
|
|
31
|
-
}): (req: Request) => Promise<void>;
|
|
42
|
+
}): (req: Request, res: ServerResponse, next: (error?: unknown) => void) => Promise<void>;
|
|
32
43
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asValue } from '@occupop/lib-container';
|
|
2
|
-
import
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
3
|
function getToken(req) {
|
|
4
4
|
return req.headers.authorization?.replace(/^Bearer /, '');
|
|
5
5
|
}
|
|
@@ -9,7 +9,7 @@ function checkToken(token) {
|
|
|
9
9
|
if (!process.env.JWT_SECRET)
|
|
10
10
|
throw 'Env var JWT_SECRET not set?';
|
|
11
11
|
try {
|
|
12
|
-
return verify(token, process.env.JWT_SECRET);
|
|
12
|
+
return jwt.verify(token, process.env.JWT_SECRET);
|
|
13
13
|
}
|
|
14
14
|
catch (e) {
|
|
15
15
|
return null;
|
|
@@ -31,6 +31,19 @@ export function getScopedUser(req) {
|
|
|
31
31
|
return user;
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Signs `authUser` with `JWT_SECRET` (HS256), for calling another service on the user's behalf.
|
|
36
|
+
* The payload is signed as given: an `iat` or `exp` it carries is kept, jsonwebtoken adds `iat`
|
|
37
|
+
* only when it has none, and no expiry, issuer or audience is added. The token is the one
|
|
38
|
+
* `@occupop/lib-context` 2.0.1's `makeGetAuthUserToken` produces. Returns `null` for no user.
|
|
39
|
+
*/
|
|
40
|
+
export function signAuthUserToken(authUser) {
|
|
41
|
+
if (!authUser)
|
|
42
|
+
return null;
|
|
43
|
+
if (!process.env.JWT_SECRET)
|
|
44
|
+
throw 'Env var JWT_SECRET not set?';
|
|
45
|
+
return jwt.sign(authUser, process.env.JWT_SECRET);
|
|
46
|
+
}
|
|
34
47
|
export function makeAuthContextValueFunction({ container }) {
|
|
35
48
|
return async ({ req }) => {
|
|
36
49
|
const authUser = getAuthUser(req);
|
|
@@ -43,10 +56,22 @@ export function makeAuthContextValueFunction({ container }) {
|
|
|
43
56
|
return { container: scopedContainer };
|
|
44
57
|
};
|
|
45
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Express middleware: attaches the request-scoped container as `req.container`, then calls
|
|
61
|
+
* `next()`. If building the context throws, it calls `next(error)` instead.
|
|
62
|
+
*/
|
|
46
63
|
export function makeAuthContextMiddleware({ container, }) {
|
|
47
|
-
return async (req) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
return async (req, res, next) => {
|
|
65
|
+
try {
|
|
66
|
+
const authContextValueFunction = makeAuthContextValueFunction({
|
|
67
|
+
container,
|
|
68
|
+
});
|
|
69
|
+
const contextValue = await authContextValueFunction({ req });
|
|
70
|
+
req.container = contextValue.container;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return next(error);
|
|
74
|
+
}
|
|
75
|
+
next();
|
|
51
76
|
};
|
|
52
77
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@occupop/lib-auth",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "index.ts",
|
|
6
6
|
"description": "...",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"test": "bun test",
|
|
13
13
|
"cover": "bun test --coverage",
|
|
14
14
|
"lint": "biome lint",
|
|
15
|
-
"
|
|
15
|
+
"test:node": "node test/node-load.mjs",
|
|
16
|
+
"dist": "bun lint && bun cover && rm -fr dist && tsc && bun run test:node"
|
|
16
17
|
},
|
|
17
18
|
"type": "module",
|
|
18
19
|
"types": "dist/index.d.ts",
|
|
@@ -20,11 +21,12 @@
|
|
|
20
21
|
"jsonwebtoken": "^9.0.2"
|
|
21
22
|
},
|
|
22
23
|
"peerDependencies": {
|
|
23
|
-
"@occupop/lib-container": "^
|
|
24
|
-
"typescript": "^5.6.3"
|
|
24
|
+
"@occupop/lib-container": "^4.0.1",
|
|
25
|
+
"typescript": "^5.6.3 || ^6.0.0"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@biomejs/biome": "^1.9.4",
|
|
29
|
+
"@occupop/lib-container": "^4.0.1",
|
|
28
30
|
"@types/bun": "latest",
|
|
29
31
|
"@types/jsonwebtoken": "^9.0.7"
|
|
30
32
|
}
|