@riao/authn-oauth2-client-cred 1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # auth-template - Changelog
package/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2022 auth-template and others
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # @riao/authn-oauth2-client-cred
2
+
3
+ OAuth2 Client Credentials (server-to-server) authentication driver for the RIAO Identity and Access Management framework.
4
+
5
+ ## Overview
6
+
7
+ This driver implements the **OAuth2 Client Credentials flow**, enabling secure server-to-server API authentication. Each client application receives credentials (client_id and client_secret) that it can exchange for short-lived JWT access tokens.
8
+
9
+ **Key Features:**
10
+ - Client credential management with bcrypt hashing
11
+ - JWT token generation with configurable expiration
12
+ - Token signature verification for security
13
+ - Multiple credentials per principal (user/service)
14
+ - Credential revocation support
15
+ - Principal validation to prevent orphaned credentials
16
+ - Client ID format validation
17
+ - Usage tracking (last exchange time, failed attempt counter)
18
+ - Cryptographically secure secret generation utility
19
+ - Database persistence
20
+ - TypeScript support with full type safety
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install @riao/authn-oauth2-client-cred @riao/iam @riao/dbal
26
+ npm install --save-dev @riao/cli
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Database Setup
32
+
33
+ Create a migration to import the OAuth2 tables:
34
+
35
+ ```bash
36
+ npx riao migration:create import-oauth2-client-cred-tables
37
+ ```
38
+
39
+ In your migration file:
40
+
41
+ ```typescript
42
+ import { OAuth2ClientCredentialsMigrations } from '@riao/authn-oauth2-client-cred';
43
+
44
+ export default OAuth2ClientCredentialsMigrations;
45
+ ```
46
+
47
+ Run migrations:
48
+
49
+ ```bash
50
+ npx riao migration:run
51
+ ```
52
+
53
+ ### 2. Initialize Authentication
54
+
55
+ ```typescript
56
+ import { OAuth2ClientCredentialsAuthentication } from '@riao/authn-oauth2-client-cred';
57
+ import { Principal } from '@riao/iam/auth';
58
+
59
+ // Define your principal/user interface
60
+ interface User extends Principal {
61
+ login: string;
62
+ }
63
+
64
+ // Create concrete authentication instance
65
+ class UserOAuth2Authentication extends OAuth2ClientCredentialsAuthentication<User> {}
66
+
67
+ const jwtSecret = process.env.JWT_SECRET;
68
+ if (!jwtSecret || jwtSecret.length < 32) {
69
+ throw new Error('JWT_SECRET must contain at least 32 characters');
70
+ }
71
+
72
+ const oauth2 = new UserOAuth2Authentication({
73
+ db: database,
74
+ jwtSecret,
75
+ tokenExpiresIn: 3600, // 1 hour
76
+ });
77
+
78
+ // Access principals via the inherited principalRepo property
79
+ const userId = 'user-123';
80
+ const principal = await oauth2.principalRepo.findOne({
81
+ where: { id: userId },
82
+ });
83
+ ```
84
+
85
+ ### 3. Create Client Credentials
86
+
87
+ ```typescript
88
+ // Generate a cryptographically secure secret
89
+ import { generateSecret } from '@riao/authn-oauth2-client-cred';
90
+
91
+ const clientSecret = generateSecret(32); // 32-character random string
92
+
93
+ // Create credentials for a principal
94
+ await oauth2.createClientCredential({
95
+ principalId: userId,
96
+ clientId: 'my-app-client',
97
+ clientSecret,
98
+ description: 'My Application',
99
+ });
100
+ ```
101
+
102
+ **Client ID Validation:**
103
+ - Must be 3-255 characters long
104
+ - Can only contain: alphanumeric, hyphens (`-`), underscores (`_`), dots (`.`), colons (`:`)
105
+ - Examples: `my-app`, `service_prod.v2`, `client:region:001`
106
+
107
+ **Principal Validation:**
108
+ - The principal ID must exist and be active
109
+ - If the principal doesn't exist, credential creation fails with a clear error
110
+
111
+ **Client Secret Validation:**
112
+ - Client secrets must be at least 32 characters long. Use a cryptographically secure random value such as one returned by `generateSecret()`.
113
+
114
+ **Throws:** Error if:
115
+ - Client ID format is invalid
116
+ - Client secret is shorter than 32 characters
117
+ - Client ID is already in use (globally unique)
118
+ - Principal doesn't exist or is inactive
119
+
120
+ ### 4. Exchange Credentials for Token
121
+
122
+ Server-to-server authentication flow:
123
+
124
+ ```typescript
125
+ // Exchange client credentials for access token
126
+ const tokenResponse = await oauth2.exchangeCredentials(
127
+ 'my-app-client',
128
+ 'super-secret-password'
129
+ );
130
+
131
+ console.log(tokenResponse);
132
+ // {
133
+ // access_token: 'eyJhbGc...',
134
+ // token_type: 'Bearer',
135
+ // expires_in: 3600
136
+ // }
137
+ ```
138
+
139
+ ### 5. Verify Access Tokens
140
+
141
+ On the API side, verify received tokens:
142
+
143
+ ```typescript
144
+ const payload = await oauth2.verifyAccessToken(accessToken);
145
+
146
+ if (payload) {
147
+ console.log('Token is valid');
148
+ console.log('Client ID:', payload.client_id);
149
+ console.log('Principal ID:', payload.principal_id);
150
+ } else {
151
+ console.log('Token is invalid or expired');
152
+ }
153
+ ```
154
+
155
+ ## API Reference
156
+
157
+ ### `generateSecret(length?)`
158
+
159
+ Generate a cryptographically secure random secret for client credentials.
160
+
161
+ **Parameters:**
162
+ - `length` (number, optional) - Length of secret in characters (default: 32)
163
+
164
+ **Returns:** Alphanumeric random string of specified length
165
+
166
+ **Example:**
167
+ ```typescript
168
+ import { generateSecret } from '@riao/authn-oauth2-client-cred';
169
+
170
+ const secret = generateSecret(32);
171
+ // 'aBcDeFgHiJkLmNoPqRsTuVwXyZ123456'
172
+ ```
173
+
174
+ ### `exchangeCredentials(clientId, clientSecret)`
175
+
176
+ Exchange client credentials for a JWT access token.
177
+
178
+ **Parameters:**
179
+ - `clientId` (string) - Unique client identifier
180
+ - `clientSecret` (string) - Client secret (will be compared against bcrypt hash)
181
+
182
+ **Returns:** Object with `access_token`, `token_type: 'Bearer'`, and `expires_in` (seconds)
183
+
184
+ **Tracking:**
185
+ - Updates `last_exchange_timestamp` on successful exchange
186
+ - Increments `failed_exchange_count` on failed attempts
187
+ - Resets `failed_exchange_count` to 0 on successful exchange
188
+
189
+ **Throws:** Error if:
190
+ - Credential not found (`"Invalid credentials"`)
191
+ - Credential is revoked (`"Credential has been revoked"`)
192
+ - Secret doesn't match (`"Invalid credentials"`)
193
+ - Principal not found (rare edge case)
194
+
195
+ ### `verifyAccessToken(token)`
196
+
197
+ Verify and decode a JWT access token.
198
+
199
+ **Parameters:**
200
+ - `token` (string) - JWT access token
201
+
202
+ **Returns:** Token payload with `client_id`, `principal_id`, `iat`, `exp` if valid; null if invalid/expired
203
+
204
+ ### `createClientCredential(options)`
205
+
206
+ Create new client credentials for a principal.
207
+
208
+ **Parameters (`options`):**
209
+ - `principalId` - The principal (user/service) ID (must exist and be active)
210
+ - `clientId` - Unique identifier (3-255 chars, alphanumeric + `-`, `_`, `.`, `:`)
211
+ - `clientSecret` - Secret (will be bcrypt hashed, never stored in plain text)
212
+ - `description` - Optional description for the credential
213
+
214
+ **Returns:** Promise that resolves when credential is created
215
+
216
+ **Validation:**
217
+ - Principal must exist and be active
218
+ - Client ID must follow format rules
219
+ - Client ID must be globally unique (cannot reuse revoked client IDs)
220
+
221
+ **Throws:** Error if:
222
+ - Principal doesn't exist or is inactive
223
+ - Client ID format is invalid
224
+ - Client ID is already in use
225
+
226
+ ### `revokeClientCredential(credentialId)`
227
+
228
+ Revoke a specific credential, preventing future token generation.
229
+
230
+ ### `revokeAllCredentials(principalId)`
231
+
232
+ Revoke all active credentials for a principal.
233
+
234
+ ### `listCredentials(principalId)`
235
+
236
+ List all active credentials for a principal (secrets are not included).
237
+
238
+ ## Usage Tracking & Monitoring
239
+
240
+ Credentials automatically track exchange activity for security monitoring:
241
+
242
+ ### Fields
243
+
244
+ - **`last_exchange_timestamp`** - When the credential was last used successfully
245
+ - **`failed_exchange_count`** - Number of failed authentication attempts since last success
246
+
247
+ ### Example: Detect Compromised Credentials
248
+
249
+ ```typescript
250
+ const credentials = await oauth2.listCredentials(principalId);
251
+
252
+ for (const cred of credentials) {
253
+ // Alert if there are many failed attempts
254
+ if (cred.failed_exchange_count > 5) {
255
+ console.warn(`Suspicious activity on ${cred.client_id}: ${cred.failed_exchange_count} failed attempts`);
256
+ // Consider revoking the credential
257
+ await oauth2.revokeClientCredential(cred.id);
258
+ }
259
+
260
+ // Alert if not used recently
261
+ if (cred.last_exchange_timestamp) {
262
+ const daysSinceUse = (Date.now() - cred.last_exchange_timestamp.getTime()) / (1000 * 60 * 60 * 24);
263
+ if (daysSinceUse > 30) {
264
+ console.info(`Credential ${cred.client_id} unused for ${Math.floor(daysSinceUse)} days`);
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## Security Considerations
271
+
272
+ ### Production Checklist
273
+
274
+ - ✅ Store `jwtSecret` in environment variables, never in code
275
+ - ✅ Use strong, random JWT secrets (32+ characters)
276
+ - ✅ Set appropriate `tokenExpiresIn` (recommend 1 hour for short-lived tokens)
277
+ - ✅ Keep client secrets confidential—display only when created
278
+ - ✅ Implement credential rotation policies
279
+ - ✅ Monitor and audit credential usage
280
+ - ✅ Revoke credentials when applications are decommissioned
281
+ - ✅ Use HTTPS for all token exchanges
282
+
283
+ ### Token Expiration
284
+
285
+ - Default: 3600 seconds (1 hour)
286
+ - Short-lived tokens (< 1 hour) recommended
287
+ - Clients should request new tokens before expiration
288
+ - Expired tokens cannot be refreshed—must exchange credentials again
289
+
290
+ ### Secret Hashing
291
+
292
+ - Client secrets are hashed using bcrypt before storage
293
+ - Secrets are never returned from the API
294
+ - Only the principal who created the credential knows the secret
295
+
296
+ ## Examples
297
+
298
+ See [examples/](examples/) for complete, runnable examples:
299
+
300
+ **CLI Example** (`server-example.ts`)
301
+ - Demonstrates all OAuth2 operations
302
+ - Shows credential lifecycle (create, exchange, verify, revoke)
303
+ - Useful for understanding the authentication flow
304
+ - Run: `npm start`
305
+
306
+ ## Contributing & Development
307
+
308
+ See [CONTRIBUTING.md](docs/contributing/contributing.md) for information on development.
@@ -0,0 +1,32 @@
1
+ export interface ClientCredential {
2
+ id: string;
3
+ principal_id: string;
4
+ client_id: string;
5
+ client_secret_hash: string;
6
+ description?: string | null;
7
+ create_timestamp: Date;
8
+ deactivate_timestamp?: Date | null;
9
+ last_exchange_timestamp?: Date | null;
10
+ failed_exchange_count: number;
11
+ locked_until?: Date | null;
12
+ }
13
+ /**
14
+ * Client ID validation constraints
15
+ */
16
+ export declare const CLIENT_ID_VALIDATION: {
17
+ readonly MIN_LENGTH: 3;
18
+ readonly MAX_LENGTH: 255;
19
+ readonly PATTERN: RegExp;
20
+ };
21
+ /**
22
+ * Client secret validation constraints
23
+ */
24
+ export declare const CLIENT_SECRET_VALIDATION: {
25
+ readonly MIN_LENGTH: 32;
26
+ };
27
+ /**
28
+ * Generate a cryptographically secure random secret
29
+ * @param length Length of secret to generate (default: 32)
30
+ * @returns Random hexadecimal secret of the requested length
31
+ */
32
+ export declare function generateSecret(length?: number): string;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSecret = exports.CLIENT_SECRET_VALIDATION = exports.CLIENT_ID_VALIDATION = void 0;
4
+ /**
5
+ * OAuth2 Client Credential Record
6
+ * Stored in database to track client credentials and their lifecycle
7
+ */
8
+ const node_crypto_1 = require("node:crypto");
9
+ /**
10
+ * Client ID validation constraints
11
+ */
12
+ exports.CLIENT_ID_VALIDATION = {
13
+ MIN_LENGTH: 3,
14
+ MAX_LENGTH: 255,
15
+ // Alphanumeric, hyphens, underscores, dots, colons
16
+ PATTERN: /^[a-zA-Z0-9._:-]+$/,
17
+ };
18
+ /**
19
+ * Client secret validation constraints
20
+ */
21
+ exports.CLIENT_SECRET_VALIDATION = {
22
+ MIN_LENGTH: 32,
23
+ };
24
+ /**
25
+ * Generate a cryptographically secure random secret
26
+ * @param length Length of secret to generate (default: 32)
27
+ * @returns Random hexadecimal secret of the requested length
28
+ */
29
+ function generateSecret(length = 32) {
30
+ const byteLength = Math.ceil(length / 2);
31
+ return (0, node_crypto_1.randomBytes)(byteLength).toString('hex').slice(0, length);
32
+ }
33
+ exports.generateSecret = generateSecret;
34
+ //# sourceMappingURL=client-credential.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-credential.js","sourceRoot":"","sources":["../../src/client-credential.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,6CAA0C;AAe1C;;GAEG;AACU,QAAA,oBAAoB,GAAG;IACnC,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,GAAG;IACf,mDAAmD;IACnD,OAAO,EAAE,oBAAoB;CACpB,CAAC;AAEX;;GAEG;AACU,QAAA,wBAAwB,GAAG;IACvC,UAAU,EAAE,EAAE;CACL,CAAC;AAEX;;;;GAIG;AACH,SAAgB,cAAc,CAAC,SAAiB,EAAE;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEzC,OAAO,IAAA,yBAAW,EAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACjE,CAAC;AAJD,wCAIC"}
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { OAuth2ClientCredentialsAuthentication, OAuth2ClientCredentialsOptions, OAuth2TokenPayload, CreateClientCredentialOptions, } from './oauth2-client-credentials-authentication';
2
+ export { ClientCredential, CLIENT_ID_VALIDATION, CLIENT_SECRET_VALIDATION, generateSecret, } from './client-credential';
3
+ export { OAuth2ClientCredentialsMigrations, } from './oauth2-client-credentials-migrations';
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuth2ClientCredentialsMigrations = exports.generateSecret = exports.CLIENT_SECRET_VALIDATION = exports.CLIENT_ID_VALIDATION = exports.OAuth2ClientCredentialsAuthentication = void 0;
4
+ var oauth2_client_credentials_authentication_1 = require("./oauth2-client-credentials-authentication");
5
+ Object.defineProperty(exports, "OAuth2ClientCredentialsAuthentication", { enumerable: true, get: function () { return oauth2_client_credentials_authentication_1.OAuth2ClientCredentialsAuthentication; } });
6
+ var client_credential_1 = require("./client-credential");
7
+ Object.defineProperty(exports, "CLIENT_ID_VALIDATION", { enumerable: true, get: function () { return client_credential_1.CLIENT_ID_VALIDATION; } });
8
+ Object.defineProperty(exports, "CLIENT_SECRET_VALIDATION", { enumerable: true, get: function () { return client_credential_1.CLIENT_SECRET_VALIDATION; } });
9
+ Object.defineProperty(exports, "generateSecret", { enumerable: true, get: function () { return client_credential_1.generateSecret; } });
10
+ var oauth2_client_credentials_migrations_1 = require("./oauth2-client-credentials-migrations");
11
+ Object.defineProperty(exports, "OAuth2ClientCredentialsMigrations", { enumerable: true, get: function () { return oauth2_client_credentials_migrations_1.OAuth2ClientCredentialsMigrations; } });
12
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,uGAKoD;AAJnD,iKAAA,qCAAqC,OAAA;AAKtC,yDAK6B;AAH5B,yHAAA,oBAAoB,OAAA;AACpB,6HAAA,wBAAwB,OAAA;AACxB,mHAAA,cAAc,OAAA;AAEf,+FAEgD;AAD/C,yJAAA,iCAAiC,OAAA"}
@@ -0,0 +1,9 @@
1
+ import { Migration } from '@riao/dbal';
2
+ /**
3
+ * Creates the oauth2_client_credentials table
4
+ * Stores client credentials for OAuth2 authentication
5
+ */
6
+ export declare class CreateClientCredentialsTable extends Migration {
7
+ up(): Promise<void>;
8
+ down(): Promise<void>;
9
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CreateClientCredentialsTable = void 0;
4
+ const column_pack_1 = require("@riao/dbal/column-pack");
5
+ const dbal_1 = require("@riao/dbal");
6
+ /**
7
+ * Creates the oauth2_client_credentials table
8
+ * Stores client credentials for OAuth2 authentication
9
+ */
10
+ class CreateClientCredentialsTable extends dbal_1.Migration {
11
+ async up() {
12
+ await this.ddl.createTable({
13
+ name: 'oauth2_client_credentials',
14
+ columns: [
15
+ column_pack_1.UUIDKeyColumn,
16
+ {
17
+ name: 'principal_id',
18
+ type: dbal_1.ColumnType.UUID,
19
+ required: true,
20
+ fk: {
21
+ referencesTable: 'iam_principals',
22
+ referencesColumn: 'id',
23
+ onDelete: 'CASCADE',
24
+ },
25
+ },
26
+ {
27
+ name: 'client_id',
28
+ type: dbal_1.ColumnType.VARCHAR,
29
+ length: 255,
30
+ required: true,
31
+ isUnique: true,
32
+ },
33
+ {
34
+ name: 'client_secret_hash',
35
+ type: dbal_1.ColumnType.VARCHAR,
36
+ length: 255,
37
+ required: true,
38
+ },
39
+ {
40
+ name: 'description',
41
+ type: dbal_1.ColumnType.VARCHAR,
42
+ length: 500,
43
+ required: false,
44
+ },
45
+ column_pack_1.CreateTimestampColumn,
46
+ {
47
+ name: 'deactivate_timestamp',
48
+ type: dbal_1.ColumnType.TIMESTAMP,
49
+ required: false,
50
+ },
51
+ {
52
+ name: 'last_exchange_timestamp',
53
+ type: dbal_1.ColumnType.TIMESTAMP,
54
+ required: false,
55
+ },
56
+ {
57
+ name: 'failed_exchange_count',
58
+ type: dbal_1.ColumnType.INT,
59
+ required: true,
60
+ default: 0,
61
+ },
62
+ {
63
+ name: 'locked_until',
64
+ type: dbal_1.ColumnType.TIMESTAMP,
65
+ required: false,
66
+ },
67
+ ],
68
+ });
69
+ }
70
+ async down() {
71
+ await this.ddl.dropTable({
72
+ tables: 'oauth2_client_credentials',
73
+ });
74
+ }
75
+ }
76
+ exports.CreateClientCredentialsTable = CreateClientCredentialsTable;
77
+ //# sourceMappingURL=001-create-client-credentials-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"001-create-client-credentials-table.js","sourceRoot":"","sources":["../../../src/migrations/001-create-client-credentials-table.ts"],"names":[],"mappings":";;;AAAA,wDAA8E;AAC9E,qCAAmD;AAEnD;;;GAGG;AACH,MAAa,4BAA6B,SAAQ,gBAAS;IACjD,KAAK,CAAC,EAAE;QAChB,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC;YAC1B,IAAI,EAAE,2BAA2B;YACjC,OAAO,EAAE;gBACR,2BAAa;gBACb;oBACC,IAAI,EAAE,cAAc;oBACpB,IAAI,EAAE,iBAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,IAAI;oBACd,EAAE,EAAE;wBACH,eAAe,EAAE,gBAAgB;wBACjC,gBAAgB,EAAE,IAAI;wBACtB,QAAQ,EAAE,SAAS;qBACnB;iBACD;gBACD;oBACC,IAAI,EAAE,WAAW;oBACjB,IAAI,EAAE,iBAAU,CAAC,OAAO;oBACxB,MAAM,EAAE,GAAG;oBACX,QAAQ,EAAE,IAAI;oBACd,QAAQ,EAAE,IAAI;iBACd;gBACD;oBACC,IAAI,EAAE,oBAAoB;oBAC1B,IAAI,EAAE,iBAAU,CAAC,OAAO;oBACxB,MAAM,EAAE,GAAG;oBACX,QAAQ,EAAE,IAAI;iBACd;gBACD;oBACC,IAAI,EAAE,aAAa;oBACnB,IAAI,EAAE,iBAAU,CAAC,OAAO;oBACxB,MAAM,EAAE,GAAG;oBACX,QAAQ,EAAE,KAAK;iBACf;gBACD,mCAAqB;gBACrB;oBACC,IAAI,EAAE,sBAAsB;oBAC5B,IAAI,EAAE,iBAAU,CAAC,SAAS;oBAC1B,QAAQ,EAAE,KAAK;iBACf;gBACD;oBACC,IAAI,EAAE,yBAAyB;oBAC/B,IAAI,EAAE,iBAAU,CAAC,SAAS;oBAC1B,QAAQ,EAAE,KAAK;iBACf;gBACD;oBACC,IAAI,EAAE,uBAAuB;oBAC7B,IAAI,EAAE,iBAAU,CAAC,GAAG;oBACpB,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE,CAAC;iBACV;gBACD;oBACC,IAAI,EAAE,cAAc;oBACpB,IAAI,EAAE,iBAAU,CAAC,SAAS;oBAC1B,QAAQ,EAAE,KAAK;iBACf;aACD;SACD,CAAC,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QAClB,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YACxB,MAAM,EAAE,2BAA2B;SACnC,CAAC,CAAC;IACJ,CAAC;CACD;AAlED,oEAkEC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * OAuth2 Client Credentials Authentication
3
+ */
4
+ import { Jwt, JwtPayload } from '@riao/crypto';
5
+ import ms from 'ms';
6
+ import { DatabaseRecordId, QueryRepository } from '@riao/dbal';
7
+ import { Hash } from '@riao/crypto';
8
+ import { Principal } from '@riao/iam/auth';
9
+ import { Authentication, AuthenticationOptions } from '@riao/iam/authentication';
10
+ import { ClientCredential } from './client-credential';
11
+ /**
12
+ * OAuth2 Client Credentials Token Payload
13
+ */
14
+ export interface OAuth2TokenPayload extends JwtPayload {
15
+ client_id: string;
16
+ principal_id: string;
17
+ }
18
+ export interface OAuth2ClientCredentialsOptions extends AuthenticationOptions {
19
+ hash?: Hash;
20
+ maxFailedAttempts?: number;
21
+ lockoutDurationMs?: number;
22
+ /**
23
+ * JWT secret for signing tokens
24
+ * In production: use strong, random 32+ character secret
25
+ */
26
+ jwtSecret: string;
27
+ /**
28
+ * Token expiration time
29
+ * Can be ms format string ("1h", "30m", "7d") or numeric seconds
30
+ * Default: "1h" (1 hour)
31
+ */
32
+ tokenExpiresIn?: ms.StringValue | number;
33
+ /**
34
+ * JWT algorithm for signing
35
+ * Default: HS256
36
+ */
37
+ jwtAlgorithm?: 'HS256' | 'HS512';
38
+ }
39
+ /**
40
+ * Options for creating a new client credential
41
+ */
42
+ export interface CreateClientCredentialOptions {
43
+ principalId: DatabaseRecordId;
44
+ clientId: string;
45
+ clientSecret: string;
46
+ description?: string;
47
+ }
48
+ /**
49
+ * OAuth2 Client Credentials Authentication
50
+ * Implements server-to-server authentication using client_id and client_secret
51
+ *
52
+ * Features:
53
+ * - Client credential management with bcrypt hashing
54
+ * - JWT token generation with configurable expiration
55
+ * - Token validation and verification
56
+ * - Multiple credentials per principal
57
+ * - Credential revocation support
58
+ */
59
+ export declare abstract class OAuth2ClientCredentialsAuthentication<TPrincipal extends Principal> extends Authentication<TPrincipal> {
60
+ credentialsRepo: QueryRepository<ClientCredential>;
61
+ protected hash: Hash;
62
+ protected jwt: Jwt<OAuth2TokenPayload>;
63
+ protected tokenExpiresInMs: ms.StringValue;
64
+ protected tokenExpiresInSeconds: number;
65
+ protected readonly maxFailedAttempts: number;
66
+ protected readonly lockoutDurationMs: number;
67
+ constructor(options: OAuth2ClientCredentialsOptions);
68
+ /**
69
+ * Validate client_id format
70
+ * @param clientId Client identifier to validate
71
+ * @throws Error if format is invalid
72
+ */
73
+ private validateClientId;
74
+ /**
75
+ * Create a new client credential for a principal
76
+ * @param options.principalId The principal ID
77
+ * @param options.clientId Unique client identifier
78
+ * @param options.clientSecret Secret (will be hashed before storage)
79
+ * @param options.description Optional description
80
+ * @throws Error if principal doesn't exist,
81
+ * client_id is invalid, or already in use
82
+ */
83
+ createClientCredential(options: CreateClientCredentialOptions): Promise<void>;
84
+ /**
85
+ * Exchange client credentials for an access token
86
+ * Implements OAuth2 Client Credentials grant
87
+ *
88
+ * @param clientId Client identifier
89
+ * @param clientSecret Client secret
90
+ * @returns Access token and expiration info
91
+ * @throws Error if credentials are invalid or principal not found
92
+ */
93
+ exchangeCredentials(clientId: string, clientSecret: string): Promise<{
94
+ access_token: string;
95
+ token_type: 'Bearer';
96
+ expires_in: number;
97
+ }>;
98
+ protected recordExchangeFailure(credential: ClientCredential): Promise<void>;
99
+ /**
100
+ * Verify and decode an access token
101
+ * @param token JWT access token
102
+ * @returns Token payload if valid, null if invalid or expired
103
+ */
104
+ verifyAccessToken(token: string): Promise<OAuth2TokenPayload | null>;
105
+ /**
106
+ * Revoke a client credential
107
+ * Prevents future token generation with this credential
108
+ * @param credentialId The credential ID to revoke
109
+ */
110
+ revokeClientCredential(credentialId: DatabaseRecordId): Promise<void>;
111
+ /**
112
+ * Revoke all credentials for a principal
113
+ * @param principalId The principal ID
114
+ */
115
+ revokeAllCredentials(principalId: DatabaseRecordId): Promise<void>;
116
+ /**
117
+ * List all active credentials for a principal
118
+ * @param principalId The principal ID
119
+ * @returns Array of credentials (without secret hashes)
120
+ */
121
+ listCredentials(principalId: DatabaseRecordId): Promise<Omit<ClientCredential, 'client_secret_hash'>[]>;
122
+ /**
123
+ * Authenticate using OAuth2 token
124
+ * (For compatibility with Authentication interface)
125
+ */
126
+ authenticate(credentials: Partial<TPrincipal & {
127
+ client_id: string;
128
+ client_secret: string;
129
+ }>): Promise<TPrincipal | null>;
130
+ }
@@ -0,0 +1,329 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuth2ClientCredentialsAuthentication = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /**
6
+ * OAuth2 Client Credentials Authentication
7
+ */
8
+ const crypto_1 = require("@riao/crypto");
9
+ const ms_1 = tslib_1.__importDefault(require("ms"));
10
+ const crypto_2 = require("@riao/crypto");
11
+ const authentication_1 = require("@riao/iam/authentication");
12
+ const client_credential_1 = require("./client-credential");
13
+ /**
14
+ * OAuth2 Client Credentials Authentication
15
+ * Implements server-to-server authentication using client_id and client_secret
16
+ *
17
+ * Features:
18
+ * - Client credential management with bcrypt hashing
19
+ * - JWT token generation with configurable expiration
20
+ * - Token validation and verification
21
+ * - Multiple credentials per principal
22
+ * - Credential revocation support
23
+ */
24
+ class OAuth2ClientCredentialsAuthentication extends authentication_1.Authentication {
25
+ credentialsRepo;
26
+ hash;
27
+ jwt;
28
+ tokenExpiresInMs;
29
+ tokenExpiresInSeconds;
30
+ maxFailedAttempts;
31
+ lockoutDurationMs;
32
+ constructor(options) {
33
+ super(options);
34
+ this.hash = options.hash ?? new crypto_2.Hash();
35
+ this.maxFailedAttempts = Math.max(1, options.maxFailedAttempts ?? 5);
36
+ this.lockoutDurationMs = options.lockoutDurationMs ?? 15 * 60 * 1000;
37
+ // Convert tokenExpiresIn to ms format and seconds
38
+ const expiresIn = options.tokenExpiresIn ?? '1h';
39
+ this.tokenExpiresInMs =
40
+ typeof expiresIn === 'number' ? `${expiresIn}s` : expiresIn;
41
+ this.tokenExpiresInSeconds =
42
+ typeof expiresIn === 'number'
43
+ ? expiresIn
44
+ : (0, ms_1.default)(expiresIn) / 1000;
45
+ // Initialize JWT handler
46
+ this.jwt = new crypto_1.Jwt({
47
+ secret: options.jwtSecret,
48
+ expiresIn: this.tokenExpiresInMs,
49
+ algorithm: (options.jwtAlgorithm ?? 'HS256'),
50
+ });
51
+ this.credentialsRepo = options.db.getQueryRepository({
52
+ table: 'oauth2_client_credentials',
53
+ identifiedBy: 'id',
54
+ });
55
+ }
56
+ /**
57
+ * Validate client_id format
58
+ * @param clientId Client identifier to validate
59
+ * @throws Error if format is invalid
60
+ */
61
+ validateClientId(clientId) {
62
+ if (!clientId || clientId.length < client_credential_1.CLIENT_ID_VALIDATION.MIN_LENGTH) {
63
+ throw new Error('Client ID must be at least '
64
+ + client_credential_1.CLIENT_ID_VALIDATION.MIN_LENGTH
65
+ + ' characters');
66
+ }
67
+ if (clientId.length > client_credential_1.CLIENT_ID_VALIDATION.MAX_LENGTH) {
68
+ throw new Error('Client ID cannot exceed '
69
+ + client_credential_1.CLIENT_ID_VALIDATION.MAX_LENGTH
70
+ + ' characters');
71
+ }
72
+ if (!client_credential_1.CLIENT_ID_VALIDATION.PATTERN.test(clientId)) {
73
+ throw new Error('Client ID must contain only alphanumeric characters, '
74
+ + 'hyphens, underscores, dots, and colons');
75
+ }
76
+ }
77
+ /**
78
+ * Create a new client credential for a principal
79
+ * @param options.principalId The principal ID
80
+ * @param options.clientId Unique client identifier
81
+ * @param options.clientSecret Secret (will be hashed before storage)
82
+ * @param options.description Optional description
83
+ * @throws Error if principal doesn't exist,
84
+ * client_id is invalid, or already in use
85
+ */
86
+ async createClientCredential(options) {
87
+ const { principalId, clientId, clientSecret, description } = options;
88
+ // Validate client_id format
89
+ this.validateClientId(clientId);
90
+ if (clientSecret.length < client_credential_1.CLIENT_SECRET_VALIDATION.MIN_LENGTH) {
91
+ throw new Error('Client secret must be at least '
92
+ + client_credential_1.CLIENT_SECRET_VALIDATION.MIN_LENGTH
93
+ + ' characters');
94
+ }
95
+ // Verify principal exists
96
+ const principal = await this.findActivePrincipal({
97
+ where: { id: principalId },
98
+ });
99
+ if (!principal) {
100
+ throw new Error('Principal with ID "'
101
+ + principalId
102
+ + '" does not exist or is inactive');
103
+ }
104
+ // Check if client_id is already in use (active or revoked)
105
+ const existing = await this.credentialsRepo.findOne({
106
+ where: { client_id: clientId },
107
+ });
108
+ if (existing) {
109
+ throw new Error(`Client ID "${clientId}" is already in use`);
110
+ }
111
+ // Hash the client secret only after inexpensive validation succeeds
112
+ const secretHash = await this.hash.make(clientSecret);
113
+ // Store credential
114
+ await this.credentialsRepo.insertOne({
115
+ record: {
116
+ principal_id: principalId,
117
+ client_id: clientId,
118
+ client_secret_hash: secretHash,
119
+ description: description ?? null,
120
+ create_timestamp: new Date(),
121
+ failed_exchange_count: 0,
122
+ locked_until: null,
123
+ },
124
+ });
125
+ }
126
+ /**
127
+ * Exchange client credentials for an access token
128
+ * Implements OAuth2 Client Credentials grant
129
+ *
130
+ * @param clientId Client identifier
131
+ * @param clientSecret Client secret
132
+ * @returns Access token and expiration info
133
+ * @throws Error if credentials are invalid or principal not found
134
+ */
135
+ async exchangeCredentials(clientId, clientSecret) {
136
+ const attempt = {
137
+ scheme: 'oauth2-client-credentials',
138
+ subject: clientId,
139
+ };
140
+ const protection = await this.beforeAuthenticationAttempt(attempt);
141
+ if (!protection.allowed) {
142
+ throw new Error('Invalid credentials');
143
+ }
144
+ // Find credential by client_id (active or revoked)
145
+ const credential = await this.credentialsRepo.findOne({
146
+ where: { client_id: clientId },
147
+ });
148
+ // Distinguish between credential not found vs. revoked
149
+ if (!credential) {
150
+ await this.recordAuthenticationFailure(attempt);
151
+ throw new Error('Invalid credentials');
152
+ }
153
+ if (credential.deactivate_timestamp !== null) {
154
+ throw new Error('Credential has been revoked');
155
+ }
156
+ if (credential.locked_until &&
157
+ credential.locked_until <= new Date()) {
158
+ await this.credentialsRepo.update({
159
+ set: {
160
+ failed_exchange_count: 0,
161
+ locked_until: null,
162
+ },
163
+ where: { id: credential.id },
164
+ });
165
+ }
166
+ else if (credential.locked_until &&
167
+ credential.locked_until > new Date()) {
168
+ throw new Error('Invalid credentials');
169
+ }
170
+ // Verify secret and track failed attempts
171
+ const isValid = await this.hash.check(clientSecret, credential.client_secret_hash);
172
+ if (!isValid) {
173
+ await this.recordAuthenticationFailure(attempt);
174
+ await this.recordExchangeFailure(credential);
175
+ throw new Error('Invalid credentials');
176
+ }
177
+ // Get principal details
178
+ const principal = await this.findActivePrincipal({
179
+ where: { id: credential.principal_id },
180
+ });
181
+ if (!principal) {
182
+ throw new Error('Principal not found');
183
+ }
184
+ // Generate JWT token using @riao/crypto
185
+ const payload = {
186
+ client_id: clientId,
187
+ principal_id: credential.principal_id,
188
+ };
189
+ const tokenResult = await this.jwt.generateToken(payload);
190
+ // Update last exchange timestamp and reset failed attempts on success
191
+ await this.credentialsRepo.update({
192
+ set: {
193
+ last_exchange_timestamp: new Date(),
194
+ failed_exchange_count: 0,
195
+ locked_until: null,
196
+ },
197
+ where: { id: credential.id },
198
+ });
199
+ await this.recordAuthenticationSuccess(attempt);
200
+ return {
201
+ access_token: tokenResult.token,
202
+ token_type: 'Bearer',
203
+ expires_in: this.tokenExpiresInSeconds,
204
+ };
205
+ }
206
+ async recordExchangeFailure(credential) {
207
+ if (credential.locked_until &&
208
+ credential.locked_until <= new Date()) {
209
+ await this.credentialsRepo.update({
210
+ set: {
211
+ failed_exchange_count: 0,
212
+ locked_until: null,
213
+ },
214
+ where: { id: credential.id },
215
+ });
216
+ }
217
+ await this.credentialsRepo.increment({
218
+ column: 'failed_exchange_count',
219
+ where: { id: credential.id },
220
+ });
221
+ const updatedCredential = await this.credentialsRepo.findOne({
222
+ where: { id: credential.id },
223
+ });
224
+ if (updatedCredential &&
225
+ (updatedCredential.failed_exchange_count || 0) >=
226
+ this.maxFailedAttempts) {
227
+ await this.credentialsRepo.update({
228
+ set: {
229
+ locked_until: new Date(Date.now() + this.lockoutDurationMs),
230
+ },
231
+ where: { id: credential.id },
232
+ });
233
+ }
234
+ }
235
+ /**
236
+ * Verify and decode an access token
237
+ * @param token JWT access token
238
+ * @returns Token payload if valid, null if invalid or expired
239
+ */
240
+ async verifyAccessToken(token) {
241
+ let payload = null;
242
+ try {
243
+ payload = await this.jwt.decodeToken(token);
244
+ }
245
+ catch (error) {
246
+ return null;
247
+ }
248
+ // Verify credential is still active
249
+ const credential = await this.credentialsRepo.findOne({
250
+ where: {
251
+ client_id: payload.client_id,
252
+ principal_id: payload.principal_id,
253
+ deactivate_timestamp: null,
254
+ },
255
+ });
256
+ if (!credential) {
257
+ return null;
258
+ }
259
+ const principal = await this.findActivePrincipal({
260
+ where: { id: payload.principal_id },
261
+ });
262
+ if (!principal) {
263
+ return null;
264
+ }
265
+ return payload;
266
+ }
267
+ /**
268
+ * Revoke a client credential
269
+ * Prevents future token generation with this credential
270
+ * @param credentialId The credential ID to revoke
271
+ */
272
+ async revokeClientCredential(credentialId) {
273
+ await this.credentialsRepo.update({
274
+ set: { deactivate_timestamp: new Date() },
275
+ where: {
276
+ id: credentialId,
277
+ },
278
+ });
279
+ }
280
+ /**
281
+ * Revoke all credentials for a principal
282
+ * @param principalId The principal ID
283
+ */
284
+ async revokeAllCredentials(principalId) {
285
+ await this.credentialsRepo.update({
286
+ set: { deactivate_timestamp: new Date() },
287
+ where: {
288
+ principal_id: principalId,
289
+ deactivate_timestamp: null,
290
+ },
291
+ });
292
+ }
293
+ /**
294
+ * List all active credentials for a principal
295
+ * @param principalId The principal ID
296
+ * @returns Array of credentials (without secret hashes)
297
+ */
298
+ async listCredentials(principalId) {
299
+ const credentials = await this.credentialsRepo.find({
300
+ where: {
301
+ principal_id: principalId,
302
+ deactivate_timestamp: null,
303
+ },
304
+ });
305
+ // Remove sensitive hashes before returning
306
+ return credentials.map((credential) => {
307
+ const safe = { ...credential };
308
+ delete safe.client_secret_hash;
309
+ return safe;
310
+ });
311
+ }
312
+ /**
313
+ * Authenticate using OAuth2 token
314
+ * (For compatibility with Authentication interface)
315
+ */
316
+ async authenticate(credentials) {
317
+ const token = await this.exchangeCredentials(credentials.client_id, credentials.client_secret);
318
+ const payload = await this.verifyAccessToken(token.access_token);
319
+ if (!payload) {
320
+ return null;
321
+ }
322
+ const principal = await this.findActivePrincipal({
323
+ where: { id: payload.principal_id },
324
+ });
325
+ return principal;
326
+ }
327
+ }
328
+ exports.OAuth2ClientCredentialsAuthentication = OAuth2ClientCredentialsAuthentication;
329
+ //# sourceMappingURL=oauth2-client-credentials-authentication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2-client-credentials-authentication.js","sourceRoot":"","sources":["../../src/oauth2-client-credentials-authentication.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { MigrationPackage, PackagedMigrations } from '@riao/dbal';
2
+ /**
3
+ * OAuth2 Client Credentials Authentication Migrations
4
+ * Provides database schema for storing client credentials with:
5
+ * - Principal foreign key with cascade delete
6
+ * - Globally unique client_id constraint
7
+ * - Bcrypt-hashed client secrets
8
+ * - Usage tracking (last exchange timestamp,
9
+ * failed attempt counter)
10
+ * - Credential lifecycle management
11
+ * (create, deactivate timestamps)
12
+ */
13
+ export declare class OAuth2ClientCredentialsMigrations extends MigrationPackage {
14
+ package: string;
15
+ name: string;
16
+ getMigrations(): Promise<PackagedMigrations>;
17
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuth2ClientCredentialsMigrations = void 0;
4
+ const dbal_1 = require("@riao/dbal");
5
+ const _001_create_client_credentials_table_1 = require("./migrations/001-create-client-credentials-table");
6
+ /**
7
+ * OAuth2 Client Credentials Authentication Migrations
8
+ * Provides database schema for storing client credentials with:
9
+ * - Principal foreign key with cascade delete
10
+ * - Globally unique client_id constraint
11
+ * - Bcrypt-hashed client secrets
12
+ * - Usage tracking (last exchange timestamp,
13
+ * failed attempt counter)
14
+ * - Credential lifecycle management
15
+ * (create, deactivate timestamps)
16
+ */
17
+ class OAuth2ClientCredentialsMigrations extends dbal_1.MigrationPackage {
18
+ package = '@riao/' + 'authn-oauth2-client-cred';
19
+ name = '@riao/' + 'authn-oauth2-client-cred';
20
+ async getMigrations() {
21
+ return {
22
+ 'create-client-credentials-table': _001_create_client_credentials_table_1.CreateClientCredentialsTable,
23
+ };
24
+ }
25
+ }
26
+ exports.OAuth2ClientCredentialsMigrations = OAuth2ClientCredentialsMigrations;
27
+ //# sourceMappingURL=oauth2-client-credentials-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth2-client-credentials-migrations.js","sourceRoot":"","sources":["../../src/oauth2-client-credentials-migrations.ts"],"names":[],"mappings":";;;AAAA,qCAAkE;AAClE,2GAE0D;AAE1D;;;;;;;;;;GAUG;AACH,MAAa,iCAAkC,SAAQ,uBAAgB;IAC7D,OAAO,GAAG,QAAQ,GAAG,0BAA0B,CAAC;IAChD,IAAI,GAAG,QAAQ,GAAG,0BAA0B,CAAC;IAE7C,KAAK,CAAC,aAAa;QAC3B,OAAO;YACN,iCAAiC,EAAE,mEAA4B;SAC9B,CAAC;IACpC,CAAC;CACD;AATD,8EASC"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@riao/authn-oauth2-client-cred",
3
+ "version": "1.0.0",
4
+ "description": "@riao/authn-oauth2-client-cred",
5
+ "typescript-template": {
6
+ "base": "1.0.1",
7
+ "jasmine": "1.0.0",
8
+ "library": "1.0.0"
9
+ },
10
+ "scripts": {},
11
+ "dependencies": {
12
+ "@riao/crypto": "^1.1.1",
13
+ "@riao/dbal": "^3.12.0",
14
+ "@riao/iam": "^4.0.0",
15
+ "ms": "^2.1.3"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/@riao/authn-oauth2-client-cred/@riao/authn-oauth2-client-cred.git"
20
+ },
21
+ "author": "@riao/authn-oauth2-client-cred",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/@riao/authn-oauth2-client-cred/@riao/authn-oauth2-client-cred/issues"
25
+ },
26
+ "homepage": "https://github.com/@riao/authn-oauth2-client-cred/@riao/authn-oauth2-client-cred#readme",
27
+ "keywords": [],
28
+ "main": "index.js",
29
+ "types": "index.d.ts"
30
+ }