@kaapi/oauth2-auth-design 0.0.34 → 0.0.36

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,297 @@
1
+ "use strict";
2
+ var _OAuth2DesignGenerator_instances, _OAuth2DesignGenerator_values, _OAuth2DesignGenerator_getOidcAuthCodeContent;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.OAuth2DesignGenerator = void 0;
5
+ const tslib_1 = require("tslib");
6
+ const definitions_1 = require("@kaapi/cli/definitions");
7
+ const utils_1 = require("@kaapi/cli/utils");
8
+ const FLOW_OPTIONS = [
9
+ {
10
+ value: 'oidc-auth-code',
11
+ label: 'OIDC Authorization Code',
12
+ hint: ''
13
+ },
14
+ {
15
+ value: 'oidc-client-credentials',
16
+ label: 'OIDC Client Credentials',
17
+ hint: ''
18
+ },
19
+ {
20
+ value: 'oidc-device-flow',
21
+ label: 'OIDC Device Flow',
22
+ hint: ''
23
+ }
24
+ ];
25
+ class OAuth2DesignGenerator {
26
+ constructor() {
27
+ _OAuth2DesignGenerator_instances.add(this);
28
+ _OAuth2DesignGenerator_values.set(this, {
29
+ name: '',
30
+ flow: ''
31
+ });
32
+ }
33
+ get type() {
34
+ return 'auth-design';
35
+ }
36
+ get name() {
37
+ return 'oauth2-design-generator';
38
+ }
39
+ get description() {
40
+ return 'Creates an auth design based on OAuth2 specifications.';
41
+ }
42
+ get notes() {
43
+ return [
44
+ 'Allowed values for --flow:',
45
+ ...FLOW_OPTIONS.map(o => ` - ${o.value}`)
46
+ ];
47
+ }
48
+ get options() {
49
+ return {
50
+ name: 'The name of the auth design',
51
+ flow: 'The grant type flow'
52
+ };
53
+ }
54
+ init(options) {
55
+ if (typeof options['name'] == 'string') {
56
+ tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name = (0, utils_1.camelCase)(options['name']);
57
+ }
58
+ if (typeof options['flow'] == 'string') {
59
+ if (!FLOW_OPTIONS.map(v => v.value).includes(options['flow'])) {
60
+ throw new Error(`Invalid value for '--flow'. Allowed values are: ${FLOW_OPTIONS.map(v => v.value).join(', ')}.`);
61
+ }
62
+ tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").flow = options['flow'];
63
+ }
64
+ }
65
+ isValid() {
66
+ return !!tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name;
67
+ }
68
+ getFileContent() {
69
+ return tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_instances, "m", _OAuth2DesignGenerator_getOidcAuthCodeContent).call(this);
70
+ }
71
+ getQuestions() {
72
+ const r = [];
73
+ if (!tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name) {
74
+ r.push({
75
+ type: definitions_1.QuestionType.text,
76
+ options: {
77
+ message: 'The name of the auth design?',
78
+ defaultValue: 'oauth2AuthDesign',
79
+ placeholder: 'oauth2AuthDesign'
80
+ },
81
+ setValue: (pluginName) => {
82
+ tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name = (0, utils_1.camelCase)(pluginName);
83
+ }
84
+ });
85
+ }
86
+ if (!(tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").flow && FLOW_OPTIONS.map(v => v.value).includes(tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").flow))) {
87
+ r.push({
88
+ type: definitions_1.QuestionType.select,
89
+ options: {
90
+ message: 'The authorization flow?',
91
+ options: FLOW_OPTIONS,
92
+ initialValue: 'oidc-auth-code'
93
+ },
94
+ setValue: (value) => {
95
+ tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").flow = `${value}`;
96
+ }
97
+ });
98
+ }
99
+ return r;
100
+ }
101
+ getFilename() {
102
+ return (0, utils_1.kebabCase)(`${tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name}`) + '.ts';
103
+ }
104
+ }
105
+ exports.OAuth2DesignGenerator = OAuth2DesignGenerator;
106
+ _OAuth2DesignGenerator_values = new WeakMap(), _OAuth2DesignGenerator_instances = new WeakSet(), _OAuth2DesignGenerator_getOidcAuthCodeContent = function _OAuth2DesignGenerator_getOidcAuthCodeContent() {
107
+ return `// generated by @kaapi/oauth2-auth-design
108
+
109
+ import {
110
+ BearerToken,
111
+ OIDCAuthorizationCodeBuilder,
112
+ createInMemoryKeyStore,
113
+ createMatchAuthCodeResult,
114
+ OAuth2TokenResponse,
115
+ ClientSecretBasic,
116
+ ClientSecretPost,
117
+ NoneAuthMethod,
118
+ OAuth2ErrorCode,
119
+ } from '@kaapi/oauth2-auth-design';
120
+
121
+ const VALID_CLIENTS = [
122
+ {
123
+ client_id: 'service-api-client',
124
+ client_secret: 's3cr3tK3y123!',
125
+ allowed_scopes: ['openid', 'profile', 'email', 'read', 'write'],
126
+ },
127
+ ];
128
+
129
+ const REGISTERED_USERS = [{ id: 'user-1234', username: 'user', password: 'password', email: 'user@email.com' }];
130
+
131
+ const authCodesStore: Map<
132
+ string,
133
+ { clientId: string; scopes: string[]; userId: string; codeChallenge?: string | undefined }
134
+ > = new Map();
135
+
136
+ export const oidcAuthCodeBuilder = OIDCAuthorizationCodeBuilder.create()
137
+ // the name of the strategy
138
+ .strategyName('${(0, utils_1.kebabCase)(tslib_1.__classPrivateFieldGet(this, _OAuth2DesignGenerator_values, "f").name)}')
139
+ // access token TTL (used in generateToken controller)
140
+ .setTokenTtl(3600)
141
+ // activate auto parsing of access token (jwtAccessTokenPayload + createJwtAccessToken)
142
+ .useAccessTokenJwks(true)
143
+ // Client authentication methods
144
+ .addClientAuthenticationMethod(new ClientSecretBasic())
145
+ .addClientAuthenticationMethod(new ClientSecretPost())
146
+ .addClientAuthenticationMethod(new NoneAuthMethod())
147
+ .setTokenType(new BearerToken())
148
+ // Define scopes
149
+ .setScopes({
150
+ profile: 'Access to basic profile information such as name and picture.',
151
+ email: "Access to the user's email address and its verification status.",
152
+ read: 'Grants read-only access to protected resources',
153
+ write: 'Grants write access to protected resources',
154
+ })
155
+
156
+ // Authorization
157
+ .authorizationRoute<object, { Payload: { username?: string; password?: string } }>((route) =>
158
+ route
159
+ .setPath('/oauth2/v1.0/authorize')
160
+ .setUsernameField('username')
161
+ .setPasswordField('password')
162
+ .generateCode(async ({ clientId, codeChallenge, scope }, req, _h) => {
163
+ // client exists?
164
+ const client = VALID_CLIENTS.find((c) => c.client_id === clientId);
165
+ if (!client) return null;
166
+
167
+ // filter client's allowed scoped
168
+ const requestedScopes = (scope ?? '').split(/\\s+/).filter(Boolean);
169
+ const grantedScopes = requestedScopes.length
170
+ ? requestedScopes.filter((s) => client.allowed_scopes.includes(s))
171
+ : client.allowed_scopes;
172
+ if (grantedScopes.length === 0) return null;
173
+
174
+ // user exists?
175
+ const user = REGISTERED_USERS.find(
176
+ (u) => u.username === req.payload.username && u.password === req.payload.password
177
+ );
178
+ if (!user) return null;
179
+
180
+ // generate code
181
+ const code = \`auth-${Date.now()}\`;
182
+ authCodesStore.set(code, { clientId, scopes: grantedScopes, userId: user.id, codeChallenge });
183
+ return { type: 'code', value: code };
184
+ })
185
+ .finalizeAuthorization(async (ctx, _params, _req, h) => {
186
+ // redirect to callback url (could do something else depending on the code result)
187
+ const matcher = createMatchAuthCodeResult({
188
+ code: async () => h.redirect(ctx.fullRedirectUri),
189
+ continue: async () => h.redirect(ctx.fullRedirectUri),
190
+ deny: async () => h.redirect(ctx.fullRedirectUri),
191
+ });
192
+ return matcher(ctx.authorizationResult);
193
+ })
194
+ )
195
+
196
+ // Token exchange
197
+ .tokenRoute((route) =>
198
+ route.generateToken(
199
+ async ({
200
+ clientId,
201
+ ttl,
202
+ tokenType,
203
+ code,
204
+ clientSecret,
205
+ codeVerifier,
206
+ createJwtAccessToken,
207
+ createIdToken,
208
+ verifyCodeVerifier,
209
+ }) => {
210
+ const entry = authCodesStore.get(code);
211
+ if (!entry || entry.clientId !== clientId) return null;
212
+
213
+ const client = VALID_CLIENTS.find((c) => c.client_id === clientId);
214
+ if (!client) {
215
+ return {
216
+ error: OAuth2ErrorCode.INVALID_CLIENT,
217
+ error_description: 'Client authentication failed.',
218
+ };
219
+ }
220
+
221
+ if (entry.codeChallenge && codeVerifier) {
222
+ if (!verifyCodeVerifier(codeVerifier, entry.codeChallenge)) {
223
+ return {
224
+ error: OAuth2ErrorCode.INVALID_GRANT,
225
+ error_description: 'Invalid authorization grant.',
226
+ };
227
+ }
228
+ } else if (clientSecret) {
229
+ if (client.client_secret !== clientSecret) {
230
+ return {
231
+ error: OAuth2ErrorCode.INVALID_CLIENT,
232
+ error_description: 'Client authentication failed.',
233
+ };
234
+ }
235
+ } else {
236
+ return {
237
+ error: OAuth2ErrorCode.INVALID_REQUEST,
238
+ error_description: 'Missing or invalid request parameter.',
239
+ };
240
+ }
241
+
242
+ const user = REGISTERED_USERS.find((u) => u.id === entry.userId);
243
+ if (!user) {
244
+ return {
245
+ error: OAuth2ErrorCode.INVALID_GRANT,
246
+ error_description: 'Invalid authorization grant.',
247
+ };
248
+ }
249
+
250
+ // Generate a signed JWT access token
251
+ const { token: accessToken } = await createJwtAccessToken!({
252
+ sub: entry.userId,
253
+ client_id: clientId,
254
+ scope: entry.scopes,
255
+ });
256
+
257
+ // Generate a signed JWT id token
258
+ const idToken = entry.scopes.includes('openid')
259
+ ? (await createIdToken!({ sub: entry.userId, name: user.username, aud: clientId })).token
260
+ : undefined;
261
+
262
+ // Return token response
263
+ return new OAuth2TokenResponse({ access_token: accessToken })
264
+ .setExpiresIn(ttl)
265
+ .setTokenType(tokenType)
266
+ .setScope(entry.scopes)
267
+ .setIdToken(idToken);
268
+ }
269
+ )
270
+ )
271
+
272
+ // Access Token Validation
273
+ .validate(async (_req, { jwtAccessTokenPayload }) => {
274
+ if (!jwtAccessTokenPayload?.sub) return { isValid: false };
275
+ return {
276
+ isValid: true,
277
+ credentials: {
278
+ user: {
279
+ id: jwtAccessTokenPayload.sub,
280
+ clientId: jwtAccessTokenPayload.client_id,
281
+ },
282
+ scope: Array.isArray(jwtAccessTokenPayload.scope) ? jwtAccessTokenPayload.scope : [],
283
+ },
284
+ };
285
+ })
286
+
287
+ // JWKS
288
+ .jwksRoute((route) => route.setPath('/discovery/v1.0/keys')) // activates jwks uri
289
+ .setPublicKeyExpiry(86400) // 24h
290
+ .setJwksKeyStore(createInMemoryKeyStore()) // store for JWKS
291
+ .setJwksRotatorOptions({
292
+ intervalMs: 7.884e9, // 91 days
293
+ timestampStore: createInMemoryKeyStore(),
294
+ });
295
+ `;
296
+ };
297
+ //# sourceMappingURL=oauth2-design-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2-design-generator.js","sourceRoot":"","sources":["../../src/generators/oauth2-design-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { FileGenerator, FileGeneratorType, Question } from '@kaapi/cli/definitions';
2
+ export declare class OAuth2FlowGenerator implements FileGenerator {
3
+ #private;
4
+ get type(): FileGeneratorType;
5
+ get name(): 'oauth2-flow';
6
+ get description(): string;
7
+ get notes(): string[];
8
+ get options(): Record<string, string>;
9
+ init(options: Record<string, unknown>): void;
10
+ isValid(): boolean;
11
+ getFileContent(): string;
12
+ getQuestions(): Question[];
13
+ getFilename(): string;
14
+ }