@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 +1 -0
- package/LICENSE.md +20 -0
- package/README.md +308 -0
- package/client-credential.d.ts +32 -0
- package/client-credential.js +34 -0
- package/client-credential.js.map +1 -0
- package/index.d.ts +3 -0
- package/index.js +12 -0
- package/index.js.map +1 -0
- package/migrations/001-create-client-credentials-table.d.ts +9 -0
- package/migrations/001-create-client-credentials-table.js +77 -0
- package/migrations/001-create-client-credentials-table.js.map +1 -0
- package/oauth2-client-credentials-authentication.d.ts +130 -0
- package/oauth2-client-credentials-authentication.js +329 -0
- package/oauth2-client-credentials-authentication.js.map +1 -0
- package/oauth2-client-credentials-migrations.d.ts +17 -0
- package/oauth2-client-credentials-migrations.js +27 -0
- package/oauth2-client-credentials-migrations.js.map +1 -0
- package/package.json +30 -0
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
|
+
}
|