@owox/idp-owox 0.0.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.
Files changed (65) hide show
  1. package/README.md +93 -0
  2. package/dist/auth/AuthorizationStore.d.ts +37 -0
  3. package/dist/auth/AuthorizationStore.d.ts.map +1 -0
  4. package/dist/auth/AuthorizationStore.js +2 -0
  5. package/dist/auth/AuthorizationStoreFactory.d.ts +7 -0
  6. package/dist/auth/AuthorizationStoreFactory.d.ts.map +1 -0
  7. package/dist/auth/AuthorizationStoreFactory.js +18 -0
  8. package/dist/auth/MysqlAuthorizationStore.d.ts +19 -0
  9. package/dist/auth/MysqlAuthorizationStore.d.ts.map +1 -0
  10. package/dist/auth/MysqlAuthorizationStore.js +84 -0
  11. package/dist/auth/SqliteAuthorizationStore.d.ts +20 -0
  12. package/dist/auth/SqliteAuthorizationStore.d.ts.map +1 -0
  13. package/dist/auth/SqliteAuthorizationStore.js +87 -0
  14. package/dist/client/IdentityOwoxClient.d.ts +27 -0
  15. package/dist/client/IdentityOwoxClient.d.ts.map +1 -0
  16. package/dist/client/IdentityOwoxClient.js +59 -0
  17. package/dist/client/dto/idpOwoxPayloadDto.d.ts +28 -0
  18. package/dist/client/dto/idpOwoxPayloadDto.d.ts.map +1 -0
  19. package/dist/client/dto/idpOwoxPayloadDto.js +30 -0
  20. package/dist/client/dto/index.d.ts +7 -0
  21. package/dist/client/dto/index.d.ts.map +1 -0
  22. package/dist/client/dto/index.js +22 -0
  23. package/dist/client/dto/introspectionDto.d.ts +68 -0
  24. package/dist/client/dto/introspectionDto.d.ts.map +1 -0
  25. package/dist/client/dto/introspectionDto.js +17 -0
  26. package/dist/client/dto/jwksDto.d.ts +100 -0
  27. package/dist/client/dto/jwksDto.d.ts.map +1 -0
  28. package/dist/client/dto/jwksDto.js +19 -0
  29. package/dist/client/dto/revocationDto.d.ts +9 -0
  30. package/dist/client/dto/revocationDto.d.ts.map +1 -0
  31. package/dist/client/dto/revocationDto.js +2 -0
  32. package/dist/client/dto/tokenDto.d.ts +30 -0
  33. package/dist/client/dto/tokenDto.d.ts.map +1 -0
  34. package/dist/client/dto/tokenDto.js +11 -0
  35. package/dist/client/dto/tokenType.d.ts +2 -0
  36. package/dist/client/dto/tokenType.d.ts.map +1 -0
  37. package/dist/client/dto/tokenType.js +2 -0
  38. package/dist/client/index.d.ts +3 -0
  39. package/dist/client/index.d.ts.map +1 -0
  40. package/dist/client/index.js +18 -0
  41. package/dist/config.d.ts +192 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +174 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +7 -0
  47. package/dist/mappers/idpOwoxPayloadToPayloadMapper.d.ts +3 -0
  48. package/dist/mappers/idpOwoxPayloadToPayloadMapper.d.ts.map +1 -0
  49. package/dist/mappers/idpOwoxPayloadToPayloadMapper.js +17 -0
  50. package/dist/owoxIdp.d.ts +25 -0
  51. package/dist/owoxIdp.d.ts.map +1 -0
  52. package/dist/owoxIdp.js +172 -0
  53. package/dist/pkce.d.ts +21 -0
  54. package/dist/pkce.d.ts.map +1 -0
  55. package/dist/pkce.js +34 -0
  56. package/dist/token/jwksCache.d.ts +19 -0
  57. package/dist/token/jwksCache.d.ts.map +1 -0
  58. package/dist/token/jwksCache.js +41 -0
  59. package/dist/token/parseToken.d.ts +11 -0
  60. package/dist/token/parseToken.d.ts.map +1 -0
  61. package/dist/token/parseToken.js +29 -0
  62. package/dist/token/verifyJwt.d.ts +9 -0
  63. package/dist/token/verifyJwt.d.ts.map +1 -0
  64. package/dist/token/verifyJwt.js +23 -0
  65. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadIdpOwoxConfigFromEnv = exports.OwoxIdp = void 0;
4
+ var owoxIdp_1 = require("./owoxIdp");
5
+ Object.defineProperty(exports, "OwoxIdp", { enumerable: true, get: function () { return owoxIdp_1.OwoxIdp; } });
6
+ var config_1 = require("./config");
7
+ Object.defineProperty(exports, "loadIdpOwoxConfigFromEnv", { enumerable: true, get: function () { return config_1.loadIdpOwoxConfigFromEnv; } });
@@ -0,0 +1,3 @@
1
+ import { Payload } from '@owox/idp-protocol';
2
+ export declare function toPayload(input: unknown): Payload;
3
+ //# sourceMappingURL=idpOwoxPayloadToPayloadMapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idpOwoxPayloadToPayloadMapper.d.ts","sourceRoot":"","sources":["../../src/mappers/idpOwoxPayloadToPayloadMapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAiB,MAAM,oBAAoB,CAAC;AAY5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjD"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPayload = toPayload;
4
+ const client_1 = require("../client");
5
+ const idp_protocol_1 = require("@owox/idp-protocol");
6
+ const IdpOwoxToPayloadSchema = client_1.IdpOwoxPayloadSchema.transform(src => ({
7
+ userId: src.userId,
8
+ projectId: src.projectId,
9
+ email: src.userEmail,
10
+ fullName: src.userFullName,
11
+ avatar: src.userAvatar,
12
+ roles: src.roles,
13
+ projectTitle: src.projectTitle,
14
+ })).pipe(idp_protocol_1.PayloadSchema);
15
+ function toPayload(input) {
16
+ return IdpOwoxToPayloadSchema.parse(input);
17
+ }
@@ -0,0 +1,25 @@
1
+ import { AuthResult, IdpProvider, Payload } from '@owox/idp-protocol';
2
+ import e, { NextFunction } from 'express';
3
+ import { IdpOwoxConfig } from './config';
4
+ export declare class OwoxIdp implements IdpProvider {
5
+ private readonly config;
6
+ private readonly store;
7
+ private readonly identityClient;
8
+ constructor(config: IdpOwoxConfig);
9
+ initialize(): Promise<void>;
10
+ introspectToken(token: string): Promise<Payload | null>;
11
+ parseToken(token: string): Promise<Payload | null>;
12
+ refreshToken(refreshToken: string): Promise<AuthResult>;
13
+ revokeToken(token: string): Promise<void>;
14
+ shutdown(): Promise<void>;
15
+ signInMiddleware(_req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
16
+ signOutMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
17
+ userApiMiddleware(req: e.Request, res: e.Response): Promise<e.Response<Payload>>;
18
+ accessTokenMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
19
+ registerRoutes(app: e.Express): void;
20
+ private changeAuthCode;
21
+ private setTokenToCookie;
22
+ private normalizeToken;
23
+ private formatError;
24
+ }
25
+ //# sourceMappingURL=owoxIdp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owoxIdp.d.ts","sourceRoot":"","sources":["../src/owoxIdp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAiB,MAAM,oBAAoB,CAAC;AACrF,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkBzC,qBAAa,OAAQ,YAAW,WAAW;IAI7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAEvB,MAAM,EAAE,aAAa;IAKlD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAW7D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAU5C,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiBvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAInB,gBAAgB,CACpB,IAAI,EAAE,CAAC,CAAC,OAAO,EACf,GAAG,EAAE,CAAC,CAAC,QAAQ,EACf,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;IAYvB,iBAAiB,CACrB,GAAG,EAAE,CAAC,CAAC,OAAO,EACd,GAAG,EAAE,CAAC,CAAC,QAAQ,EACf,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;IASvB,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAchF,qBAAqB,CACzB,GAAG,EAAE,CAAC,CAAC,OAAO,EACd,GAAG,EAAE,CAAC,CAAC,QAAQ,EACf,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;IA0B7B,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC,OAAO,GAAG,IAAI;YAyBtB,cAAc;IAgB5B,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,WAAW;CAOpB"}
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OwoxIdp = void 0;
7
+ const idp_protocol_1 = require("@owox/idp-protocol");
8
+ const client_1 = require("./client");
9
+ const AuthorizationStoreFactory_1 = require("./auth/AuthorizationStoreFactory");
10
+ const parseToken_1 = require("./token/parseToken");
11
+ const pkce_1 = require("./pkce");
12
+ const idpOwoxPayloadToPayloadMapper_1 = require("./mappers/idpOwoxPayloadToPayloadMapper");
13
+ const ms_1 = __importDefault(require("ms"));
14
+ const COOKIE_NAME = 'refreshToken';
15
+ class OwoxIdp {
16
+ config;
17
+ store;
18
+ identityClient;
19
+ constructor(config) {
20
+ this.config = config;
21
+ this.store = (0, AuthorizationStoreFactory_1.createAuthorizationStore)(config.dbConfig);
22
+ this.identityClient = new client_1.IdentityOwoxClient(config.identityOwoxClientConfig);
23
+ }
24
+ initialize() {
25
+ return this.store.initialize();
26
+ }
27
+ async introspectToken(token) {
28
+ const request = { token: token };
29
+ const response = await this.identityClient.introspectToken(request);
30
+ if (!response.isActive) {
31
+ return null;
32
+ }
33
+ return (0, idpOwoxPayloadToPayloadMapper_1.toPayload)(response);
34
+ }
35
+ parseToken(token) {
36
+ const config = {
37
+ jwtKeyCacheTtl: this.config.jwtConfig.jwtKeyCacheTtl,
38
+ clockTolerance: this.config.jwtConfig.clockTolerance,
39
+ expectedIss: this.config.jwtConfig.issuer,
40
+ algorithm: this.config.jwtConfig.algorithm,
41
+ };
42
+ return (0, parseToken_1.parseToken)(this.normalizeToken(token), this.identityClient, config);
43
+ }
44
+ async refreshToken(refreshToken) {
45
+ const request = {
46
+ grantType: 'refresh_token',
47
+ refreshToken: refreshToken,
48
+ clientId: this.config.idpConfig.clientId,
49
+ };
50
+ const response = await this.identityClient.getToken(request);
51
+ return {
52
+ accessToken: response.accessToken,
53
+ refreshToken: response.refreshToken,
54
+ accessTokenExpiresIn: response.accessTokenExpiresIn,
55
+ refreshTokenExpiresIn: response.refreshTokenExpiresIn,
56
+ };
57
+ }
58
+ async revokeToken(token) {
59
+ const request = { token: token, tokenType: 'refresh_token' };
60
+ await this.identityClient.revokeToken(request);
61
+ }
62
+ shutdown() {
63
+ return this.store.shutdown();
64
+ }
65
+ async signInMiddleware(_req, res, _next) {
66
+ const { codeVerifier, codeChallenge } = await (0, pkce_1.generatePkce)();
67
+ const state = (0, pkce_1.generateState)();
68
+ const clientId = this.config.idpConfig.clientId;
69
+ const expiresAt = new Date(Date.now() + (0, ms_1.default)('1m'));
70
+ await this.store.save(state, codeVerifier, expiresAt);
71
+ const url = `${this.config.idpConfig.platformSignInUrl}?state=${state}&codeChallenge=${codeChallenge}&clientId=${clientId}`;
72
+ res.redirect(url);
73
+ }
74
+ async signOutMiddleware(req, res, _next) {
75
+ const refreshToken = req.cookies[COOKIE_NAME];
76
+ if (refreshToken) {
77
+ await this.revokeToken(refreshToken);
78
+ }
79
+ res.clearCookie(COOKIE_NAME);
80
+ res.redirect(idp_protocol_1.ProtocolRoute.SIGN_IN);
81
+ }
82
+ async userApiMiddleware(req, res) {
83
+ const accessToken = req.headers['x-owox-authorization'];
84
+ if (!accessToken) {
85
+ return res.status(401).json({ message: 'Unauthorized', reason: 'uam1' });
86
+ }
87
+ const payload = await this.parseToken(accessToken);
88
+ if (!payload) {
89
+ return res.status(401).json({ message: 'Unauthorized', reason: 'uam2' });
90
+ }
91
+ return res.json(payload);
92
+ }
93
+ async accessTokenMiddleware(req, res, _next) {
94
+ try {
95
+ const refreshToken = req.cookies[COOKIE_NAME];
96
+ if (!refreshToken) {
97
+ return res.json({ reason: 'atm1' });
98
+ }
99
+ const auth = await this.refreshToken(refreshToken);
100
+ const newRefreshToken = auth.refreshToken;
101
+ if (!newRefreshToken) {
102
+ return res.json({ reason: 'atm2' });
103
+ }
104
+ if (!auth.refreshTokenExpiresIn) {
105
+ return res.json({ reason: 'atm3' });
106
+ }
107
+ this.setTokenToCookie(res, req, newRefreshToken, auth.refreshTokenExpiresIn);
108
+ return res.json(auth);
109
+ }
110
+ catch (error) {
111
+ console.log(this.formatError(error));
112
+ res.clearCookie(COOKIE_NAME);
113
+ return res.json({ reason: 'atm4' });
114
+ }
115
+ }
116
+ registerRoutes(app) {
117
+ app.get(this.config.idpConfig.callbackUrl, async (req, res) => {
118
+ const code = req.query.code;
119
+ const state = req.query.state;
120
+ if (!code) {
121
+ console.log('Redirect url should contain code param');
122
+ return res.redirect(idp_protocol_1.ProtocolRoute.SIGN_IN);
123
+ }
124
+ if (!state) {
125
+ console.log('Redirect url should contain state param');
126
+ return res.redirect(idp_protocol_1.ProtocolRoute.SIGN_IN);
127
+ }
128
+ try {
129
+ const response = await this.changeAuthCode(code, state);
130
+ this.setTokenToCookie(res, req, response.refreshToken, response.refreshTokenExpiresIn);
131
+ res.redirect('/');
132
+ }
133
+ catch (error) {
134
+ console.log(this.formatError(error));
135
+ return res.redirect(idp_protocol_1.ProtocolRoute.SIGN_IN);
136
+ }
137
+ });
138
+ }
139
+ async changeAuthCode(code, state) {
140
+ const codeVerifier = await this.store.get(state);
141
+ if (!codeVerifier) {
142
+ throw Error('Code verifier is empty');
143
+ }
144
+ const request = {
145
+ grantType: 'authorization_code',
146
+ authCode: code,
147
+ codeVerifier: codeVerifier,
148
+ clientId: this.config.idpConfig.clientId,
149
+ };
150
+ return await this.identityClient.getToken(request);
151
+ }
152
+ setTokenToCookie(res, req, refreshToken, expiresIn) {
153
+ const isSecure = req.protocol !== 'http' && !(req.hostname === 'localhost' || req.hostname === '127.0.0.1');
154
+ res.cookie(COOKIE_NAME, refreshToken, {
155
+ httpOnly: true,
156
+ secure: isSecure,
157
+ sameSite: 'lax',
158
+ maxAge: expiresIn * 1000,
159
+ path: '/',
160
+ });
161
+ }
162
+ normalizeToken(authorization) {
163
+ return authorization.replace(/^Bearer\s+/i, '').trim();
164
+ }
165
+ formatError(error) {
166
+ if (error instanceof Error) {
167
+ return `${error.message}\n${error.stack ?? ''}`;
168
+ }
169
+ return String(error);
170
+ }
171
+ }
172
+ exports.OwoxIdp = OwoxIdp;
package/dist/pkce.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export interface PkceDto {
2
+ codeVerifier: string;
3
+ codeChallenge: string;
4
+ }
5
+ /**
6
+ * Generates a PKCE (Proof Key for Code Exchange) pair consisting of a code verifier and a code challenge.
7
+ *
8
+ * @param {number} [length=86] - The desired length of the code verifier. Defaults to 86 if not specified.
9
+ * @return {Promise<{codeVerifier: string, codeChallenge: string}>} A promise that resolves to an object containing the code verifier and code challenge.
10
+ */
11
+ export declare function generatePkce(length?: number): Promise<PkceDto>;
12
+ /**
13
+ * Generates a random string state encoded in Base64 URL format.
14
+ * Optionally allows truncation of the generated state to a specific length.
15
+ *
16
+ * @param {number} [bytes=32] - The number of random bytes to generate.
17
+ * @param {number} [targetLen] - The desired length of the output string. If not specified, the full generated string is returned.
18
+ * @return {string} - A randomly generated Base64 URL-encoded string. If targetLen is provided, returns a substring of the specified length.
19
+ */
20
+ export declare function generateState(bytes?: number, targetLen?: number): string;
21
+ //# sourceMappingURL=pkce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAGxE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,MAAW,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG5E"}
package/dist/pkce.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generatePkce = generatePkce;
7
+ exports.generateState = generateState;
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const pkce_challenge_1 = __importDefault(require("pkce-challenge"));
10
+ /**
11
+ * Generates a PKCE (Proof Key for Code Exchange) pair consisting of a code verifier and a code challenge.
12
+ *
13
+ * @param {number} [length=86] - The desired length of the code verifier. Defaults to 86 if not specified.
14
+ * @return {Promise<{codeVerifier: string, codeChallenge: string}>} A promise that resolves to an object containing the code verifier and code challenge.
15
+ */
16
+ async function generatePkce(length = 86) {
17
+ const { code_verifier, code_challenge } = await (0, pkce_challenge_1.default)(length);
18
+ return { codeVerifier: code_verifier, codeChallenge: code_challenge };
19
+ }
20
+ /**
21
+ * Generates a random string state encoded in Base64 URL format.
22
+ * Optionally allows truncation of the generated state to a specific length.
23
+ *
24
+ * @param {number} [bytes=32] - The number of random bytes to generate.
25
+ * @param {number} [targetLen] - The desired length of the output string. If not specified, the full generated string is returned.
26
+ * @return {string} - A randomly generated Base64 URL-encoded string. If targetLen is provided, returns a substring of the specified length.
27
+ */
28
+ function generateState(bytes = 32, targetLen) {
29
+ const state = base64url(node_crypto_1.default.randomBytes(bytes));
30
+ return targetLen ? state.slice(0, targetLen) : state;
31
+ }
32
+ function base64url(buf) {
33
+ return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
34
+ }
@@ -0,0 +1,19 @@
1
+ import { createLocalJWKSet, JWK } from 'jose';
2
+ export interface JWKSet {
3
+ keys: JWK[];
4
+ }
5
+ export interface JwksFetcher {
6
+ (): Promise<JWKSet>;
7
+ }
8
+ interface CacheEntry {
9
+ jwks: JWKSet;
10
+ keyResolver: ReturnType<typeof createLocalJWKSet>;
11
+ exp: number;
12
+ inflight?: Promise<CacheEntry>;
13
+ }
14
+ export declare function makeJwksCache(fetchJwks: JwksFetcher, key: string): {
15
+ get: (ttlMs: number) => Promise<CacheEntry>;
16
+ refresh: (ttlMs: number) => Promise<CacheEntry>;
17
+ };
18
+ export {};
19
+ //# sourceMappingURL=jwksCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwksCache.d.ts","sourceRoot":"","sources":["../../src/token/jwksCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAE9C,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM;iBA2BrC,MAAM,KAAG,OAAO,CAAC,UAAU,CAAC;qBAMxB,MAAM;EAKrC"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeJwksCache = makeJwksCache;
4
+ const jose_1 = require("jose");
5
+ function makeJwksCache(fetchJwks, key) {
6
+ const cache = new Map();
7
+ async function load(ttlMs) {
8
+ const existing = cache.get(key);
9
+ if (existing?.inflight)
10
+ return existing.inflight;
11
+ const inflight = (async () => {
12
+ const jwks = await fetchJwks();
13
+ const cacheEntry = {
14
+ jwks,
15
+ keyResolver: (0, jose_1.createLocalJWKSet)(jwks),
16
+ exp: Date.now() + ttlMs,
17
+ };
18
+ cache.set(key, cacheEntry);
19
+ return cacheEntry;
20
+ })();
21
+ cache.set(key, { ...(existing ?? {}), inflight });
22
+ try {
23
+ return await inflight;
24
+ }
25
+ finally {
26
+ const cacheEntry = cache.get(key);
27
+ if (cacheEntry)
28
+ cacheEntry.inflight = undefined;
29
+ }
30
+ }
31
+ async function get(ttlMs) {
32
+ const cacheEntry = cache.get(key);
33
+ if (!cacheEntry || cacheEntry.exp <= Date.now())
34
+ return load(ttlMs);
35
+ return cacheEntry;
36
+ }
37
+ async function refresh(ttlMs) {
38
+ return load(ttlMs);
39
+ }
40
+ return { get, refresh };
41
+ }
@@ -0,0 +1,11 @@
1
+ import { Payload } from '@owox/idp-protocol';
2
+ import { IdentityOwoxClient } from '../client';
3
+ import ms from 'ms';
4
+ export interface ParseTokenConfig {
5
+ jwtKeyCacheTtl: ms.StringValue;
6
+ clockTolerance: string | number;
7
+ expectedIss: string;
8
+ algorithm: string;
9
+ }
10
+ export declare function parseToken(token: string, client: IdentityOwoxClient, config: ParseTokenConfig): Promise<Payload>;
11
+ //# sourceMappingURL=parseToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseToken.d.ts","sourceRoot":"","sources":["../../src/token/parseToken.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,EAAE,CAAC,WAAW,CAAC;IAC/B,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseToken = parseToken;
7
+ const jose_1 = require("jose");
8
+ const jwksCache_1 = require("./jwksCache");
9
+ const verifyJwt_1 = require("./verifyJwt");
10
+ const idpOwoxPayloadToPayloadMapper_1 = require("../mappers/idpOwoxPayloadToPayloadMapper");
11
+ const ms_1 = __importDefault(require("ms"));
12
+ async function parseToken(token, client, config) {
13
+ const { alg } = (0, jose_1.decodeProtectedHeader)(token);
14
+ if (alg && alg !== config.algorithm) {
15
+ throw new Error(`Unsupported JWT alg: ${alg}`);
16
+ }
17
+ const fetchJwks = async () => {
18
+ const resp = await client.getJwks();
19
+ return { keys: resp.keys };
20
+ };
21
+ const cacheKey = 'JWKS_KEYS';
22
+ const cache = (0, jwksCache_1.makeJwksCache)(fetchJwks, cacheKey);
23
+ const { payload } = await (0, verifyJwt_1.verify)(token, async () => (await cache.get((0, ms_1.default)(config.jwtKeyCacheTtl))).keyResolver, async () => (await cache.refresh((0, ms_1.default)(config.jwtKeyCacheTtl))).keyResolver, {
24
+ algorithm: config.algorithm,
25
+ clockTolerance: config.clockTolerance,
26
+ issuer: config.expectedIss,
27
+ });
28
+ return (0, idpOwoxPayloadToPayloadMapper_1.toPayload)(payload);
29
+ }
@@ -0,0 +1,9 @@
1
+ import { createLocalJWKSet } from 'jose';
2
+ interface verifyConfig {
3
+ algorithm: string;
4
+ clockTolerance: string | number;
5
+ issuer: string;
6
+ }
7
+ export declare function verify(token: string, getKeyResolver: () => Promise<ReturnType<typeof createLocalJWKSet>>, refreshKeyResolver: () => Promise<ReturnType<typeof createLocalJWKSet>>, config: verifyConfig): Promise<import("jose", { with: { "resolution-mode": "import" } }).JWTVerifyResult<import("jose", { with: { "resolution-mode": "import" } }).JWTPayload> & import("jose", { with: { "resolution-mode": "import" } }).ResolvedKey>;
8
+ export {};
9
+ //# sourceMappingURL=verifyJwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyJwt.d.ts","sourceRoot":"","sources":["../../src/token/verifyJwt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIpD,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,EACnE,kBAAkB,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,EACvE,MAAM,EAAE,YAAY,oOAmBrB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verify = verify;
4
+ const jose_1 = require("jose");
5
+ const errors_1 = require("jose/errors");
6
+ async function verify(token, getKeyResolver, refreshKeyResolver, config) {
7
+ const jwtVerifyOptions = {
8
+ algorithms: [config.algorithm],
9
+ clockTolerance: config.clockTolerance,
10
+ issuer: config.issuer,
11
+ };
12
+ try {
13
+ const key = await getKeyResolver();
14
+ return await (0, jose_1.jwtVerify)(token, key, jwtVerifyOptions);
15
+ }
16
+ catch (err) {
17
+ const shouldRefreshKey = err instanceof errors_1.JWKSNoMatchingKey || err instanceof errors_1.JWSSignatureVerificationFailed;
18
+ if (!shouldRefreshKey)
19
+ throw err;
20
+ const keyAfterReload = await refreshKeyResolver();
21
+ return await (0, jose_1.jwtVerify)(token, keyAfterReload, jwtVerifyOptions);
22
+ }
23
+ }
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@owox/idp-owox",
3
+ "version": "0.0.0",
4
+ "description": "Identity Provider implementation from OWOX",
5
+ "author": "OWOX",
6
+ "license": "ELv2",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "build:dep": "npm run build:idp-protocol",
12
+ "build:idp-protocol": "npm run build -w @owox/idp-protocol --prefix ../..",
13
+ "clean": "rimraf dist",
14
+ "test": "jest",
15
+ "lint": "eslint . --config ./eslint.config.js",
16
+ "lint:fix": "eslint . --fix --config ./eslint.config.js",
17
+ "format": "prettier --write \"**/*.{ts,js,json,md}\"",
18
+ "format:check": "prettier --check \"**/*.{ts,js,json,md}\"",
19
+ "typecheck": "tsc --noEmit",
20
+ "prepack": "npm run build",
21
+ "prepublishOnly": "npm audit && npm run lint && npm run typecheck"
22
+ },
23
+ "dependencies": {
24
+ "@owox/idp-protocol": "0.5.0",
25
+ "pkce-challenge": "^5.0.0",
26
+ "cookie-parser": "^1.4.7",
27
+ "env-paths": "^3.0.0",
28
+ "jose": "^6.0.12",
29
+ "ms": "^2.1.3",
30
+ "better-sqlite3": "^12.2.0",
31
+ "mysql2": "^3.14.3"
32
+ },
33
+ "devDependencies": {
34
+ "@types/cookie-parser": "^1.4.9",
35
+ "@types/better-sqlite3": "^7.6.13",
36
+ "@types/express": "^5.0.0",
37
+ "@types/node": "^22.10.7",
38
+ "typescript": "^5.7.3",
39
+ "eslint": "^9.20.1",
40
+ "prettier": "^3.5.3",
41
+ "ts-jest": "^29.2.5"
42
+ },
43
+ "peerDependencies": {
44
+ "zod": "^3.25.67"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.16.0"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "files": [
53
+ "dist"
54
+ ],
55
+ "exports": {
56
+ ".": {
57
+ "import": "./dist/index.js",
58
+ "require": "./dist/index.js",
59
+ "types": "./dist/index.d.ts"
60
+ }
61
+ }
62
+ }