@quatrain/auth 1.2.9 → 1.2.10
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/dist/AbstractAuthAdapter.d.ts +6 -0
- package/dist/AbstractAuthAdapter.js +8 -0
- package/dist/AbstractOAuthAdapter.d.ts +95 -0
- package/dist/AbstractOAuthAdapter.js +215 -0
- package/dist/Auth.d.ts +8 -0
- package/dist/Auth.js +16 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/package.json +2 -2
- package/src/AbstractAuthAdapter.ts +9 -0
- package/src/AbstractOAuthAdapter.ts +182 -0
- package/src/Auth.ts +17 -0
- package/src/index.ts +2 -0
|
@@ -109,4 +109,10 @@ export declare abstract class AbstractAuthAdapter implements AuthInterface {
|
|
|
109
109
|
* @param redirectTo - Optional redirect destination.
|
|
110
110
|
*/
|
|
111
111
|
recoverPassword(email: string, redirectTo?: string): Promise<any>;
|
|
112
|
+
/**
|
|
113
|
+
* Returns the pluggable endpoint handler function for this adapter, if any.
|
|
114
|
+
*
|
|
115
|
+
* @returns The EndpointHandler callback or null.
|
|
116
|
+
*/
|
|
117
|
+
getEndpointHandler(): any;
|
|
112
118
|
}
|
|
@@ -82,6 +82,14 @@ class AbstractAuthAdapter {
|
|
|
82
82
|
recoverPassword(email, redirectTo) {
|
|
83
83
|
throw new Error('Password recovery not implemented for this adapter');
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns the pluggable endpoint handler function for this adapter, if any.
|
|
87
|
+
*
|
|
88
|
+
* @returns The EndpointHandler callback or null.
|
|
89
|
+
*/
|
|
90
|
+
getEndpointHandler() {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
94
|
exports.AbstractAuthAdapter = AbstractAuthAdapter;
|
|
87
95
|
/** The `User` class reference to be used by the adapter. */
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter';
|
|
2
|
+
import { AuthParameters } from './Auth';
|
|
3
|
+
import { User } from '@quatrain/backend';
|
|
4
|
+
/**
|
|
5
|
+
* Abstract class summarizing typical OAuth2 Web Application Flows.
|
|
6
|
+
* Extend this to implement providers like GitHub, GitLab, Google, etc.
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class AbstractOAuthAdapter extends AbstractAuthAdapter {
|
|
9
|
+
protected abstract _authorizationEndpoint: string;
|
|
10
|
+
protected abstract _tokenEndpoint: string;
|
|
11
|
+
protected abstract _userProfileEndpoint: string;
|
|
12
|
+
constructor(params?: AuthParameters);
|
|
13
|
+
/**
|
|
14
|
+
* Returns the authorization redirect URL.
|
|
15
|
+
*
|
|
16
|
+
* @param redirectUri - The callback URL.
|
|
17
|
+
* @param scopes - The requested authorization scopes.
|
|
18
|
+
* @param state - The state parameters for CSRF protection.
|
|
19
|
+
* @returns The generated authorization URL.
|
|
20
|
+
*/
|
|
21
|
+
getAuthorizationUrl(redirectUri?: string, scopes?: string[], state?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Exchanges the temporary authorization code for an access token packet.
|
|
24
|
+
*
|
|
25
|
+
* @param code - The temporary auth code.
|
|
26
|
+
* @param redirectUri - Optional redirect URL context.
|
|
27
|
+
* @returns Access token payload.
|
|
28
|
+
*/
|
|
29
|
+
exchangeCodeForToken(code: string, redirectUri?: string): Promise<any>;
|
|
30
|
+
/**
|
|
31
|
+
* Registers a new user account (unsupported for OAuth adapters).
|
|
32
|
+
*
|
|
33
|
+
* @param user - Target user entity.
|
|
34
|
+
* @param clearPassword - Optional cleartext password.
|
|
35
|
+
* @returns Throws an error indicating registration is unsupported.
|
|
36
|
+
*/
|
|
37
|
+
register(user: User, clearPassword?: string): Promise<any>;
|
|
38
|
+
/**
|
|
39
|
+
* Performs user signup with credentials (unsupported for OAuth adapters).
|
|
40
|
+
*
|
|
41
|
+
* @param login - Login identifier string.
|
|
42
|
+
* @param password - Password string.
|
|
43
|
+
* @returns Throws an error indicating signup should use exchangeCodeForToken.
|
|
44
|
+
*/
|
|
45
|
+
signup(login: string, password: string): Promise<any>;
|
|
46
|
+
/**
|
|
47
|
+
* Signs out the specified user session.
|
|
48
|
+
*
|
|
49
|
+
* @param user - Target user entity.
|
|
50
|
+
* @returns Promise resolving to true.
|
|
51
|
+
*/
|
|
52
|
+
signout(user: User): Promise<any>;
|
|
53
|
+
/**
|
|
54
|
+
* Updates user profile attributes in the auth store.
|
|
55
|
+
*
|
|
56
|
+
* @param user - Target user entity.
|
|
57
|
+
* @param updatable - Attributes payload to update.
|
|
58
|
+
* @returns Promise resolving to true.
|
|
59
|
+
*/
|
|
60
|
+
update(user: User, updatable: any): Promise<any>;
|
|
61
|
+
/**
|
|
62
|
+
* Deletes a user account from the auth store.
|
|
63
|
+
*
|
|
64
|
+
* @param user - Target user entity.
|
|
65
|
+
* @returns Promise resolving to true.
|
|
66
|
+
*/
|
|
67
|
+
delete(user: User): Promise<any>;
|
|
68
|
+
/**
|
|
69
|
+
* Refreshes an expired access token using a refresh token string.
|
|
70
|
+
*
|
|
71
|
+
* @param refreshToken - Target refresh token.
|
|
72
|
+
* @returns Throws an error if refresh token logic is not implemented by the provider.
|
|
73
|
+
*/
|
|
74
|
+
refreshToken(refreshToken: string): Promise<any>;
|
|
75
|
+
/**
|
|
76
|
+
* Revokes the access token (noop default).
|
|
77
|
+
*
|
|
78
|
+
* @param token - Target token to revoke.
|
|
79
|
+
*/
|
|
80
|
+
revokeAuthToken(token: string): Promise<any>;
|
|
81
|
+
/**
|
|
82
|
+
* Inject custom claims (not supported by default).
|
|
83
|
+
*
|
|
84
|
+
* @param id - Target user ID.
|
|
85
|
+
* @param claims - Payload claims.
|
|
86
|
+
*/
|
|
87
|
+
setCustomUserClaims(id: string, claims: any): Promise<any>;
|
|
88
|
+
/**
|
|
89
|
+
* Retrieves and validates an auth token payload for the given raw token string.
|
|
90
|
+
*
|
|
91
|
+
* @param token - Raw authorization token string.
|
|
92
|
+
* @returns The resolved token payload or user claims.
|
|
93
|
+
*/
|
|
94
|
+
abstract getAuthToken(token: string): any;
|
|
95
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.AbstractOAuthAdapter = void 0;
|
|
46
|
+
const AbstractAuthAdapter_1 = require("./AbstractAuthAdapter");
|
|
47
|
+
const nativeFetch = __importStar(require("node-fetch-native"));
|
|
48
|
+
/**
|
|
49
|
+
* Abstract class summarizing typical OAuth2 Web Application Flows.
|
|
50
|
+
* Extend this to implement providers like GitHub, GitLab, Google, etc.
|
|
51
|
+
*/
|
|
52
|
+
class AbstractOAuthAdapter extends AbstractAuthAdapter_1.AbstractAuthAdapter {
|
|
53
|
+
constructor(params = {}) {
|
|
54
|
+
super(params);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns the authorization redirect URL.
|
|
58
|
+
*
|
|
59
|
+
* @param redirectUri - The callback URL.
|
|
60
|
+
* @param scopes - The requested authorization scopes.
|
|
61
|
+
* @param state - The state parameters for CSRF protection.
|
|
62
|
+
* @returns The generated authorization URL.
|
|
63
|
+
*/
|
|
64
|
+
getAuthorizationUrl(redirectUri, scopes = [], state) {
|
|
65
|
+
var _a;
|
|
66
|
+
const clientId = (_a = this._params.config) === null || _a === void 0 ? void 0 : _a.clientId;
|
|
67
|
+
if (!clientId) {
|
|
68
|
+
throw new Error(`[${this.constructor.name}] Client ID is not configured.`);
|
|
69
|
+
}
|
|
70
|
+
let url = `${this._authorizationEndpoint}?client_id=${clientId}`;
|
|
71
|
+
if (scopes.length > 0) {
|
|
72
|
+
url += `&scope=${encodeURIComponent(scopes.join(' '))}`;
|
|
73
|
+
}
|
|
74
|
+
if (redirectUri) {
|
|
75
|
+
url += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
76
|
+
}
|
|
77
|
+
if (state) {
|
|
78
|
+
url += `&state=${encodeURIComponent(state)}`;
|
|
79
|
+
}
|
|
80
|
+
return url;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Exchanges the temporary authorization code for an access token packet.
|
|
84
|
+
*
|
|
85
|
+
* @param code - The temporary auth code.
|
|
86
|
+
* @param redirectUri - Optional redirect URL context.
|
|
87
|
+
* @returns Access token payload.
|
|
88
|
+
*/
|
|
89
|
+
exchangeCodeForToken(code, redirectUri) {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
const clientId = (_a = this._params.config) === null || _a === void 0 ? void 0 : _a.clientId;
|
|
93
|
+
const clientSecret = (_b = this._params.config) === null || _b === void 0 ? void 0 : _b.clientSecret;
|
|
94
|
+
if (!clientId || !clientSecret) {
|
|
95
|
+
throw new Error(`[${this.constructor.name}] Client ID or Client Secret is not configured.`);
|
|
96
|
+
}
|
|
97
|
+
const body = {
|
|
98
|
+
client_id: clientId,
|
|
99
|
+
client_secret: clientSecret,
|
|
100
|
+
code,
|
|
101
|
+
grant_type: 'authorization_code'
|
|
102
|
+
};
|
|
103
|
+
if (redirectUri) {
|
|
104
|
+
body.redirect_uri = redirectUri;
|
|
105
|
+
}
|
|
106
|
+
const response = yield nativeFetch.fetch(this._tokenEndpoint, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
'Accept': 'application/json'
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify(body)
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error(`[${this.constructor.name}] OAuth token exchange failed: ${response.statusText}`);
|
|
116
|
+
}
|
|
117
|
+
const data = yield response.json();
|
|
118
|
+
if (data.error) {
|
|
119
|
+
throw new Error(`[${this.constructor.name}] OAuth error: ${data.error_description || data.error}`);
|
|
120
|
+
}
|
|
121
|
+
return data; // Access token, scopes, token_type, etc.
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Registers a new user account (unsupported for OAuth adapters).
|
|
126
|
+
*
|
|
127
|
+
* @param user - Target user entity.
|
|
128
|
+
* @param clearPassword - Optional cleartext password.
|
|
129
|
+
* @returns Throws an error indicating registration is unsupported.
|
|
130
|
+
*/
|
|
131
|
+
register(user, clearPassword) {
|
|
132
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
133
|
+
throw new Error(`register is not supported by ${this.constructor.name}`);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Performs user signup with credentials (unsupported for OAuth adapters).
|
|
138
|
+
*
|
|
139
|
+
* @param login - Login identifier string.
|
|
140
|
+
* @param password - Password string.
|
|
141
|
+
* @returns Throws an error indicating signup should use exchangeCodeForToken.
|
|
142
|
+
*/
|
|
143
|
+
signup(login, password) {
|
|
144
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
throw new Error(`signup is not supported by ${this.constructor.name}. Use exchangeCodeForToken instead.`);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Signs out the specified user session.
|
|
150
|
+
*
|
|
151
|
+
* @param user - Target user entity.
|
|
152
|
+
* @returns Promise resolving to true.
|
|
153
|
+
*/
|
|
154
|
+
signout(user) {
|
|
155
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Updates user profile attributes in the auth store.
|
|
161
|
+
*
|
|
162
|
+
* @param user - Target user entity.
|
|
163
|
+
* @param updatable - Attributes payload to update.
|
|
164
|
+
* @returns Promise resolving to true.
|
|
165
|
+
*/
|
|
166
|
+
update(user, updatable) {
|
|
167
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Deletes a user account from the auth store.
|
|
173
|
+
*
|
|
174
|
+
* @param user - Target user entity.
|
|
175
|
+
* @returns Promise resolving to true.
|
|
176
|
+
*/
|
|
177
|
+
delete(user) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
return true;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Refreshes an expired access token using a refresh token string.
|
|
184
|
+
*
|
|
185
|
+
* @param refreshToken - Target refresh token.
|
|
186
|
+
* @returns Throws an error if refresh token logic is not implemented by the provider.
|
|
187
|
+
*/
|
|
188
|
+
refreshToken(refreshToken) {
|
|
189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
throw new Error(`refreshToken is not implemented for ${this.constructor.name}`);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Revokes the access token (noop default).
|
|
195
|
+
*
|
|
196
|
+
* @param token - Target token to revoke.
|
|
197
|
+
*/
|
|
198
|
+
revokeAuthToken(token) {
|
|
199
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
200
|
+
return true;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Inject custom claims (not supported by default).
|
|
205
|
+
*
|
|
206
|
+
* @param id - Target user ID.
|
|
207
|
+
* @param claims - Payload claims.
|
|
208
|
+
*/
|
|
209
|
+
setCustomUserClaims(id, claims) {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
throw new Error(`setCustomUserClaims is not supported by ${this.constructor.name}`);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.AbstractOAuthAdapter = AbstractOAuthAdapter;
|
package/dist/Auth.d.ts
CHANGED
|
@@ -56,4 +56,12 @@ export declare class Auth extends Core {
|
|
|
56
56
|
* @throws {Error} If the specified alias is unknown.
|
|
57
57
|
*/
|
|
58
58
|
static getProvider<T extends AbstractAuthAdapter>(alias?: string): T;
|
|
59
|
+
/**
|
|
60
|
+
* Scans all registered auth providers, collects their endpoint handlers,
|
|
61
|
+
* and registers them dynamically under a common routing root path.
|
|
62
|
+
*
|
|
63
|
+
* @param server - The Quatrain ServerAdapter (Astro, Express, etc.).
|
|
64
|
+
* @param rootPath - The common authentication root segment (defaults to '/api/auth').
|
|
65
|
+
*/
|
|
66
|
+
static registerEndpoints(server: any, rootPath?: string): void;
|
|
59
67
|
}
|
package/dist/Auth.js
CHANGED
|
@@ -42,6 +42,22 @@ class Auth extends core_1.Core {
|
|
|
42
42
|
throw new Error(`Unknown provider alias: '${alias}'`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Scans all registered auth providers, collects their endpoint handlers,
|
|
47
|
+
* and registers them dynamically under a common routing root path.
|
|
48
|
+
*
|
|
49
|
+
* @param server - The Quatrain ServerAdapter (Astro, Express, etc.).
|
|
50
|
+
* @param rootPath - The common authentication root segment (defaults to '/api/auth').
|
|
51
|
+
*/
|
|
52
|
+
static registerEndpoints(server, rootPath = '/api/auth') {
|
|
53
|
+
for (const [alias, provider] of Object.entries(this._providers)) {
|
|
54
|
+
const handler = provider.getEndpointHandler();
|
|
55
|
+
if (handler) {
|
|
56
|
+
this.info(`Registering pluggable API endpoints for auth provider '${alias}' on '${rootPath}/${alias}'`);
|
|
57
|
+
server.addEndpoint(handler, `${rootPath}/${alias}`, { adapter: provider });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
exports.Auth = Auth;
|
|
47
63
|
_a = Auth;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Auth, AuthAction } from './Auth';
|
|
2
2
|
import type { AuthParameters, AuthParametersKeys } from './Auth';
|
|
3
3
|
import { AbstractAuthAdapter } from './AbstractAuthAdapter';
|
|
4
|
+
import { AbstractOAuthAdapter } from './AbstractOAuthAdapter';
|
|
4
5
|
import type { AuthInterface } from './types/AuthInterface';
|
|
5
6
|
import { AuthenticationError } from './AuthenticationError';
|
|
6
|
-
export { Auth, AuthAction, AbstractAuthAdapter, AuthenticationError, };
|
|
7
|
+
export { Auth, AuthAction, AbstractAuthAdapter, AbstractOAuthAdapter, AuthenticationError, };
|
|
7
8
|
export type { AuthParameters, AuthParametersKeys, AuthInterface, };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AuthenticationError = exports.AbstractAuthAdapter = exports.AuthAction = exports.Auth = void 0;
|
|
3
|
+
exports.AuthenticationError = exports.AbstractOAuthAdapter = exports.AbstractAuthAdapter = exports.AuthAction = exports.Auth = void 0;
|
|
4
4
|
const Auth_1 = require("./Auth");
|
|
5
5
|
Object.defineProperty(exports, "Auth", { enumerable: true, get: function () { return Auth_1.Auth; } });
|
|
6
6
|
Object.defineProperty(exports, "AuthAction", { enumerable: true, get: function () { return Auth_1.AuthAction; } });
|
|
7
7
|
const AbstractAuthAdapter_1 = require("./AbstractAuthAdapter");
|
|
8
8
|
Object.defineProperty(exports, "AbstractAuthAdapter", { enumerable: true, get: function () { return AbstractAuthAdapter_1.AbstractAuthAdapter; } });
|
|
9
|
+
const AbstractOAuthAdapter_1 = require("./AbstractOAuthAdapter");
|
|
10
|
+
Object.defineProperty(exports, "AbstractOAuthAdapter", { enumerable: true, get: function () { return AbstractOAuthAdapter_1.AbstractOAuthAdapter; } });
|
|
9
11
|
const AuthenticationError_1 = require("./AuthenticationError");
|
|
10
12
|
Object.defineProperty(exports, "AuthenticationError", { enumerable: true, get: function () { return AuthenticationError_1.AuthenticationError; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/auth",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"description": "Auth adapters commons",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@quatrain/api": "^1.1.7",
|
|
24
24
|
"@quatrain/backend": "^1.2.17",
|
|
25
|
-
"@quatrain/core": "^1.2.
|
|
25
|
+
"@quatrain/core": "^1.2.17",
|
|
26
26
|
"@quatrain/http": "^1.0.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
@@ -158,4 +158,13 @@ export abstract class AbstractAuthAdapter implements AuthInterface {
|
|
|
158
158
|
recoverPassword(email: string, redirectTo?: string): Promise<any> {
|
|
159
159
|
throw new Error('Password recovery not implemented for this adapter')
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Returns the pluggable endpoint handler function for this adapter, if any.
|
|
164
|
+
*
|
|
165
|
+
* @returns The EndpointHandler callback or null.
|
|
166
|
+
*/
|
|
167
|
+
public getEndpointHandler(): any {
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
161
170
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { AbstractAuthAdapter } from './AbstractAuthAdapter'
|
|
2
|
+
import { AuthParameters } from './Auth'
|
|
3
|
+
import { User } from '@quatrain/backend'
|
|
4
|
+
import * as nativeFetch from 'node-fetch-native'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Abstract class summarizing typical OAuth2 Web Application Flows.
|
|
8
|
+
* Extend this to implement providers like GitHub, GitLab, Google, etc.
|
|
9
|
+
*/
|
|
10
|
+
export abstract class AbstractOAuthAdapter extends AbstractAuthAdapter {
|
|
11
|
+
protected abstract _authorizationEndpoint: string
|
|
12
|
+
protected abstract _tokenEndpoint: string
|
|
13
|
+
protected abstract _userProfileEndpoint: string
|
|
14
|
+
|
|
15
|
+
constructor(params: AuthParameters = {}) {
|
|
16
|
+
super(params)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the authorization redirect URL.
|
|
21
|
+
*
|
|
22
|
+
* @param redirectUri - The callback URL.
|
|
23
|
+
* @param scopes - The requested authorization scopes.
|
|
24
|
+
* @param state - The state parameters for CSRF protection.
|
|
25
|
+
* @returns The generated authorization URL.
|
|
26
|
+
*/
|
|
27
|
+
public getAuthorizationUrl(redirectUri?: string, scopes: string[] = [], state?: string): string {
|
|
28
|
+
const clientId = this._params.config?.clientId
|
|
29
|
+
if (!clientId) {
|
|
30
|
+
throw new Error(`[${this.constructor.name}] Client ID is not configured.`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let url = `${this._authorizationEndpoint}?client_id=${clientId}`
|
|
34
|
+
if (scopes.length > 0) {
|
|
35
|
+
url += `&scope=${encodeURIComponent(scopes.join(' '))}`
|
|
36
|
+
}
|
|
37
|
+
if (redirectUri) {
|
|
38
|
+
url += `&redirect_uri=${encodeURIComponent(redirectUri)}`
|
|
39
|
+
}
|
|
40
|
+
if (state) {
|
|
41
|
+
url += `&state=${encodeURIComponent(state)}`
|
|
42
|
+
}
|
|
43
|
+
return url
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Exchanges the temporary authorization code for an access token packet.
|
|
48
|
+
*
|
|
49
|
+
* @param code - The temporary auth code.
|
|
50
|
+
* @param redirectUri - Optional redirect URL context.
|
|
51
|
+
* @returns Access token payload.
|
|
52
|
+
*/
|
|
53
|
+
public async exchangeCodeForToken(code: string, redirectUri?: string): Promise<any> {
|
|
54
|
+
const clientId = this._params.config?.clientId
|
|
55
|
+
const clientSecret = this._params.config?.clientSecret
|
|
56
|
+
|
|
57
|
+
if (!clientId || !clientSecret) {
|
|
58
|
+
throw new Error(`[${this.constructor.name}] Client ID or Client Secret is not configured.`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const body: Record<string, string> = {
|
|
62
|
+
client_id: clientId,
|
|
63
|
+
client_secret: clientSecret,
|
|
64
|
+
code,
|
|
65
|
+
grant_type: 'authorization_code'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (redirectUri) {
|
|
69
|
+
body.redirect_uri = redirectUri
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await nativeFetch.fetch(this._tokenEndpoint, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
'Accept': 'application/json'
|
|
77
|
+
},
|
|
78
|
+
body: JSON.stringify(body)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`[${this.constructor.name}] OAuth token exchange failed: ${response.statusText}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const data = await response.json()
|
|
86
|
+
if (data.error) {
|
|
87
|
+
throw new Error(`[${this.constructor.name}] OAuth error: ${data.error_description || data.error}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return data // Access token, scopes, token_type, etc.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Registers a new user account (unsupported for OAuth adapters).
|
|
95
|
+
*
|
|
96
|
+
* @param user - Target user entity.
|
|
97
|
+
* @param clearPassword - Optional cleartext password.
|
|
98
|
+
* @returns Throws an error indicating registration is unsupported.
|
|
99
|
+
*/
|
|
100
|
+
async register(user: User, clearPassword?: string): Promise<any> {
|
|
101
|
+
throw new Error(`register is not supported by ${this.constructor.name}`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Performs user signup with credentials (unsupported for OAuth adapters).
|
|
106
|
+
*
|
|
107
|
+
* @param login - Login identifier string.
|
|
108
|
+
* @param password - Password string.
|
|
109
|
+
* @returns Throws an error indicating signup should use exchangeCodeForToken.
|
|
110
|
+
*/
|
|
111
|
+
async signup(login: string, password: string): Promise<any> {
|
|
112
|
+
throw new Error(`signup is not supported by ${this.constructor.name}. Use exchangeCodeForToken instead.`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Signs out the specified user session.
|
|
117
|
+
*
|
|
118
|
+
* @param user - Target user entity.
|
|
119
|
+
* @returns Promise resolving to true.
|
|
120
|
+
*/
|
|
121
|
+
async signout(user: User): Promise<any> {
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Updates user profile attributes in the auth store.
|
|
127
|
+
*
|
|
128
|
+
* @param user - Target user entity.
|
|
129
|
+
* @param updatable - Attributes payload to update.
|
|
130
|
+
* @returns Promise resolving to true.
|
|
131
|
+
*/
|
|
132
|
+
async update(user: User, updatable: any): Promise<any> {
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Deletes a user account from the auth store.
|
|
138
|
+
*
|
|
139
|
+
* @param user - Target user entity.
|
|
140
|
+
* @returns Promise resolving to true.
|
|
141
|
+
*/
|
|
142
|
+
async delete(user: User): Promise<any> {
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Refreshes an expired access token using a refresh token string.
|
|
148
|
+
*
|
|
149
|
+
* @param refreshToken - Target refresh token.
|
|
150
|
+
* @returns Throws an error if refresh token logic is not implemented by the provider.
|
|
151
|
+
*/
|
|
152
|
+
async refreshToken(refreshToken: string): Promise<any> {
|
|
153
|
+
throw new Error(`refreshToken is not implemented for ${this.constructor.name}`)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Revokes the access token (noop default).
|
|
158
|
+
*
|
|
159
|
+
* @param token - Target token to revoke.
|
|
160
|
+
*/
|
|
161
|
+
async revokeAuthToken(token: string): Promise<any> {
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Inject custom claims (not supported by default).
|
|
167
|
+
*
|
|
168
|
+
* @param id - Target user ID.
|
|
169
|
+
* @param claims - Payload claims.
|
|
170
|
+
*/
|
|
171
|
+
async setCustomUserClaims(id: string, claims: any): Promise<any> {
|
|
172
|
+
throw new Error(`setCustomUserClaims is not supported by ${this.constructor.name}`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Retrieves and validates an auth token payload for the given raw token string.
|
|
177
|
+
*
|
|
178
|
+
* @param token - Raw authorization token string.
|
|
179
|
+
* @returns The resolved token payload or user claims.
|
|
180
|
+
*/
|
|
181
|
+
abstract getAuthToken(token: string): any
|
|
182
|
+
}
|
package/src/Auth.ts
CHANGED
|
@@ -87,4 +87,21 @@ export class Auth extends Core {
|
|
|
87
87
|
throw new Error(`Unknown provider alias: '${alias}'`)
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Scans all registered auth providers, collects their endpoint handlers,
|
|
93
|
+
* and registers them dynamically under a common routing root path.
|
|
94
|
+
*
|
|
95
|
+
* @param server - The Quatrain ServerAdapter (Astro, Express, etc.).
|
|
96
|
+
* @param rootPath - The common authentication root segment (defaults to '/api/auth').
|
|
97
|
+
*/
|
|
98
|
+
static registerEndpoints(server: any, rootPath: string = '/api/auth') {
|
|
99
|
+
for (const [alias, provider] of Object.entries(this._providers)) {
|
|
100
|
+
const handler = provider.getEndpointHandler()
|
|
101
|
+
if (handler) {
|
|
102
|
+
this.info(`Registering pluggable API endpoints for auth provider '${alias}' on '${rootPath}/${alias}'`)
|
|
103
|
+
server.addEndpoint(handler, `${rootPath}/${alias}`, { adapter: provider })
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
90
107
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Auth, AuthAction } from './Auth'
|
|
2
2
|
import type { AuthParameters, AuthParametersKeys } from './Auth'
|
|
3
3
|
import { AbstractAuthAdapter } from './AbstractAuthAdapter'
|
|
4
|
+
import { AbstractOAuthAdapter } from './AbstractOAuthAdapter'
|
|
4
5
|
import type { AuthInterface } from './types/AuthInterface'
|
|
5
6
|
import { AuthenticationError } from './AuthenticationError'
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ export {
|
|
|
8
9
|
Auth,
|
|
9
10
|
AuthAction,
|
|
10
11
|
AbstractAuthAdapter,
|
|
12
|
+
AbstractOAuthAdapter,
|
|
11
13
|
AuthenticationError,
|
|
12
14
|
}
|
|
13
15
|
|