@boxyhq/saml-jackson 1.0.7 → 1.1.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/dist/controller/api.js +2 -2
- package/dist/controller/oauth.js +57 -4
- package/dist/controller/oidc-discovery.d.ts +50 -0
- package/dist/controller/oidc-discovery.js +49 -0
- package/dist/controller/utils.d.ts +9 -0
- package/dist/controller/utils.js +42 -1
- package/dist/db/sql/sql.js +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/typings.d.ts +28 -0
- package/package.json +11 -10
package/dist/controller/api.js
CHANGED
@@ -187,7 +187,7 @@ class APIController {
|
|
187
187
|
if (encodedRawMetadata) {
|
188
188
|
metaData = Buffer.from(encodedRawMetadata, 'base64').toString();
|
189
189
|
}
|
190
|
-
const idpMetadata = yield saml20_1.default.
|
190
|
+
const idpMetadata = yield saml20_1.default.parseMetadata(metaData, {});
|
191
191
|
// extract provider
|
192
192
|
let providerName = extractHostName(idpMetadata.entityID);
|
193
193
|
if (!providerName) {
|
@@ -323,7 +323,7 @@ class APIController {
|
|
323
323
|
}
|
324
324
|
let newMetadata;
|
325
325
|
if (metaData) {
|
326
|
-
newMetadata = yield saml20_1.default.
|
326
|
+
newMetadata = yield saml20_1.default.parseMetadata(metaData, {});
|
327
327
|
// extract provider
|
328
328
|
let providerName = extractHostName(newMetadata.entityID);
|
329
329
|
if (!providerName) {
|
package/dist/controller/oauth.js
CHANGED
@@ -39,6 +39,7 @@ exports.OAuthController = void 0;
|
|
39
39
|
const crypto_1 = __importDefault(require("crypto"));
|
40
40
|
const util_1 = require("util");
|
41
41
|
const zlib_1 = require("zlib");
|
42
|
+
const jose = __importStar(require("jose"));
|
42
43
|
const dbutils = __importStar(require("../db/utils"));
|
43
44
|
const metrics = __importStar(require("../opentelemetry/metrics"));
|
44
45
|
const saml20_1 = __importDefault(require("@boxyhq/saml20"));
|
@@ -78,6 +79,9 @@ function getEncodedTenantProduct(param) {
|
|
78
79
|
return null;
|
79
80
|
}
|
80
81
|
}
|
82
|
+
function getScopeValues(scope) {
|
83
|
+
return typeof scope === 'string' ? scope.split(' ').filter((s) => s.length > 0) : [];
|
84
|
+
}
|
81
85
|
class OAuthController {
|
82
86
|
constructor({ configStore, sessionStore, codeStore, tokenStore, opts }) {
|
83
87
|
this.configStore = configStore;
|
@@ -128,7 +132,7 @@ class OAuthController {
|
|
128
132
|
}
|
129
133
|
authorize(body) {
|
130
134
|
return __awaiter(this, void 0, void 0, function* () {
|
131
|
-
const { response_type = 'code', client_id, redirect_uri, state, tenant, product, access_type, scope, code_challenge, code_challenge_method = '',
|
135
|
+
const { response_type = 'code', client_id, redirect_uri, state, tenant, product, access_type, scope, nonce, code_challenge, code_challenge_method = '',
|
132
136
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
133
137
|
provider = 'saml', idp_hint, } = body;
|
134
138
|
let requestedTenant = tenant;
|
@@ -138,6 +142,8 @@ class OAuthController {
|
|
138
142
|
throw new error_1.JacksonError('Please specify a redirect URL.', 400);
|
139
143
|
}
|
140
144
|
let samlConfig;
|
145
|
+
const requestedScopes = getScopeValues(scope);
|
146
|
+
const requestedOIDCFlow = requestedScopes.includes('openid');
|
141
147
|
if (tenant && product) {
|
142
148
|
const samlConfigs = yield this.configStore.getByIndex({
|
143
149
|
name: utils_1.IndexNames.TenantProduct,
|
@@ -172,8 +178,11 @@ class OAuthController {
|
|
172
178
|
if (!sp && access_type) {
|
173
179
|
sp = getEncodedTenantProduct(access_type);
|
174
180
|
}
|
175
|
-
if (!sp &&
|
176
|
-
|
181
|
+
if (!sp && requestedScopes) {
|
182
|
+
const encodedParams = requestedScopes.find((scope) => scope.includes('=') && scope.includes('&')); // for now assume only one encoded param i.e. for tenant/product
|
183
|
+
if (encodedParams) {
|
184
|
+
sp = getEncodedTenantProduct(encodedParams);
|
185
|
+
}
|
177
186
|
}
|
178
187
|
if (sp && sp.tenant && sp.product) {
|
179
188
|
requestedTenant = sp.tenant;
|
@@ -222,6 +231,16 @@ class OAuthController {
|
|
222
231
|
if (!allowed.redirect(redirect_uri, samlConfig.redirectUrl)) {
|
223
232
|
throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
|
224
233
|
}
|
234
|
+
if (requestedOIDCFlow &&
|
235
|
+
(!this.opts.openid.jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(this.opts.openid.jwtSigningKeys))) {
|
236
|
+
return {
|
237
|
+
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
238
|
+
error: 'server_error',
|
239
|
+
error_description: 'OAuth server not configured correctly for openid flow, check if JWT signing keys are loaded',
|
240
|
+
redirect_uri,
|
241
|
+
}),
|
242
|
+
};
|
243
|
+
}
|
225
244
|
if (!state) {
|
226
245
|
return {
|
227
246
|
redirect_url: (0, utils_1.OAuthErrorResponse)({
|
@@ -282,6 +301,15 @@ class OAuthController {
|
|
282
301
|
if (idp_hint) {
|
283
302
|
requested.idp_hint = idp_hint;
|
284
303
|
}
|
304
|
+
if (requestedOIDCFlow) {
|
305
|
+
requested.oidc = true;
|
306
|
+
if (nonce) {
|
307
|
+
requested.nonce = nonce;
|
308
|
+
}
|
309
|
+
}
|
310
|
+
if (requestedScopes) {
|
311
|
+
requested.scope = requestedScopes;
|
312
|
+
}
|
285
313
|
yield this.sessionStore.put(sessionId, {
|
286
314
|
id: samlReq.id,
|
287
315
|
redirect_uri,
|
@@ -563,6 +591,27 @@ class OAuthController {
|
|
563
591
|
// store details against a token
|
564
592
|
const token = crypto_1.default.randomBytes(20).toString('hex');
|
565
593
|
const tokenVal = Object.assign(Object.assign({}, codeVal.profile), { requested: codeVal.requested });
|
594
|
+
const requestedOIDCFlow = !!codeVal.requested.oidc;
|
595
|
+
const requestHasNonce = !!codeVal.requested.nonce;
|
596
|
+
if (requestedOIDCFlow) {
|
597
|
+
const { jwtSigningKeys, jwsAlg } = this.opts.openid;
|
598
|
+
if (!jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(jwtSigningKeys)) {
|
599
|
+
throw new error_1.JacksonError('JWT signing keys are not loaded', 500);
|
600
|
+
}
|
601
|
+
let claims = requestHasNonce ? { nonce: codeVal.requested.nonce } : {};
|
602
|
+
claims = Object.assign(Object.assign({}, claims), { id: codeVal.profile.claims.id, email: codeVal.profile.claims.email, firstName: codeVal.profile.claims.firstName, lastName: codeVal.profile.claims.lastName });
|
603
|
+
const signingKey = yield (0, utils_1.loadJWSPrivateKey)(jwtSigningKeys.private, jwsAlg);
|
604
|
+
const id_token = yield new jose.SignJWT(claims)
|
605
|
+
.setProtectedHeader({ alg: jwsAlg })
|
606
|
+
.setIssuedAt()
|
607
|
+
.setIssuer(this.opts.samlAudience || '')
|
608
|
+
.setSubject(codeVal.profile.claims.id)
|
609
|
+
.setAudience(tokenVal.requested.client_id)
|
610
|
+
.setExpirationTime(`${this.opts.db.ttl}s`) // identity token only really needs to be valid long enough for it to be verified by the client application.
|
611
|
+
.sign(signingKey);
|
612
|
+
tokenVal.id_token = id_token;
|
613
|
+
tokenVal.claims.sub = codeVal.profile.claims.id;
|
614
|
+
}
|
566
615
|
yield this.tokenStore.put(token, tokenVal);
|
567
616
|
// delete the code
|
568
617
|
try {
|
@@ -571,11 +620,15 @@ class OAuthController {
|
|
571
620
|
catch (_err) {
|
572
621
|
// ignore error
|
573
622
|
}
|
574
|
-
|
623
|
+
const tokenResponse = {
|
575
624
|
access_token: token,
|
576
625
|
token_type: 'bearer',
|
577
626
|
expires_in: this.opts.db.ttl,
|
578
627
|
};
|
628
|
+
if (requestedOIDCFlow) {
|
629
|
+
tokenResponse.id_token = tokenVal.id_token;
|
630
|
+
}
|
631
|
+
return tokenResponse;
|
579
632
|
});
|
580
633
|
}
|
581
634
|
/**
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import { IOidcDiscoveryController } from '../typings';
|
2
|
+
export declare class OidcDiscoveryController implements IOidcDiscoveryController {
|
3
|
+
private opts;
|
4
|
+
constructor({ opts }: {
|
5
|
+
opts: any;
|
6
|
+
});
|
7
|
+
openidConfig(): {
|
8
|
+
issuer: string;
|
9
|
+
authorization_endpoint: string;
|
10
|
+
token_endpoint: string;
|
11
|
+
userinfo_endpoint: string;
|
12
|
+
jwks_uri: string;
|
13
|
+
response_types_supported: string[];
|
14
|
+
subject_types_supported: string[];
|
15
|
+
id_token_signing_alg_values_supported: string[];
|
16
|
+
grant_types_supported: string[];
|
17
|
+
code_challenge_methods_supported: string[];
|
18
|
+
};
|
19
|
+
jwks(): Promise<{
|
20
|
+
keys: {
|
21
|
+
kid: string;
|
22
|
+
alg: string;
|
23
|
+
use: string;
|
24
|
+
crv?: string | undefined;
|
25
|
+
d?: string | undefined;
|
26
|
+
dp?: string | undefined;
|
27
|
+
dq?: string | undefined;
|
28
|
+
e?: string | undefined;
|
29
|
+
ext?: boolean | undefined;
|
30
|
+
k?: string | undefined;
|
31
|
+
key_ops?: string[] | undefined;
|
32
|
+
kty?: string | undefined;
|
33
|
+
n?: string | undefined;
|
34
|
+
oth?: {
|
35
|
+
d?: string | undefined;
|
36
|
+
r?: string | undefined;
|
37
|
+
t?: string | undefined;
|
38
|
+
}[] | undefined;
|
39
|
+
p?: string | undefined;
|
40
|
+
q?: string | undefined;
|
41
|
+
qi?: string | undefined;
|
42
|
+
x?: string | undefined;
|
43
|
+
y?: string | undefined;
|
44
|
+
x5c?: string[] | undefined;
|
45
|
+
x5t?: string | undefined;
|
46
|
+
'x5t#S256'?: string | undefined;
|
47
|
+
x5u?: string | undefined;
|
48
|
+
}[];
|
49
|
+
}>;
|
50
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
12
|
+
exports.OidcDiscoveryController = void 0;
|
13
|
+
const error_1 = require("./error");
|
14
|
+
const utils_1 = require("./utils");
|
15
|
+
class OidcDiscoveryController {
|
16
|
+
constructor({ opts }) {
|
17
|
+
this.opts = opts;
|
18
|
+
}
|
19
|
+
openidConfig() {
|
20
|
+
return {
|
21
|
+
issuer: this.opts.samlAudience,
|
22
|
+
authorization_endpoint: `${this.opts.externalUrl}/api/oauth/authorize`,
|
23
|
+
token_endpoint: `${this.opts.externalUrl}/api/oauth/token`,
|
24
|
+
userinfo_endpoint: `${this.opts.externalUrl}/api/oauth/userinfo`,
|
25
|
+
jwks_uri: `${this.opts.externalUrl}/oauth/jwks`,
|
26
|
+
response_types_supported: ['code'],
|
27
|
+
subject_types_supported: ['public'],
|
28
|
+
id_token_signing_alg_values_supported: ['RS256'],
|
29
|
+
grant_types_supported: ['authorization_code'],
|
30
|
+
code_challenge_methods_supported: ['plain', 'S256'],
|
31
|
+
};
|
32
|
+
}
|
33
|
+
jwks() {
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
35
|
+
const { jwtSigningKeys, jwsAlg } = this.opts.openid;
|
36
|
+
if (!jwtSigningKeys || !(0, utils_1.isJWSKeyPairLoaded)(jwtSigningKeys)) {
|
37
|
+
throw new error_1.JacksonError('JWT signing keys are not loaded', 501);
|
38
|
+
}
|
39
|
+
const importedPublicKey = yield (0, utils_1.importJWTPublicKey)(jwtSigningKeys.public, jwsAlg);
|
40
|
+
const publicKeyJWK = yield (0, utils_1.exportPublicKeyJWK)(importedPublicKey);
|
41
|
+
const jwkThumbprint = yield (0, utils_1.generateJwkThumbprint)(publicKeyJWK);
|
42
|
+
const jwks = {
|
43
|
+
keys: [Object.assign(Object.assign({}, publicKeyJWK), { kid: jwkThumbprint, alg: jwsAlg, use: 'sig' })],
|
44
|
+
};
|
45
|
+
return jwks;
|
46
|
+
});
|
47
|
+
}
|
48
|
+
}
|
49
|
+
exports.OidcDiscoveryController = OidcDiscoveryController;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import type { OAuthErrorHandlerParams } from '../typings';
|
2
|
+
import * as jose from 'jose';
|
2
3
|
export declare enum IndexNames {
|
3
4
|
EntityID = "entityID",
|
4
5
|
TenantProduct = "tenantProduct"
|
@@ -7,3 +8,11 @@ export declare const relayStatePrefix = "boxyhq_jackson_";
|
|
7
8
|
export declare const validateAbsoluteUrl: (url: any, message: any) => void;
|
8
9
|
export declare const OAuthErrorResponse: ({ error, error_description, redirect_uri, state, }: OAuthErrorHandlerParams) => string;
|
9
10
|
export declare function getErrorMessage(error: unknown): string;
|
11
|
+
export declare function loadJWSPrivateKey(key: string, alg: string): Promise<jose.KeyLike>;
|
12
|
+
export declare function isJWSKeyPairLoaded(jwsKeyPair: {
|
13
|
+
private: string;
|
14
|
+
public: string;
|
15
|
+
}): boolean;
|
16
|
+
export declare const importJWTPublicKey: (key: string, jwsAlg: string) => Promise<jose.KeyLike>;
|
17
|
+
export declare const exportPublicKeyJWK: (key: jose.KeyLike) => Promise<jose.JWK>;
|
18
|
+
export declare const generateJwkThumbprint: (jwk: jose.JWK) => Promise<string>;
|
package/dist/controller/utils.js
CHANGED
@@ -22,10 +22,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
22
22
|
__setModuleDefault(result, mod);
|
23
23
|
return result;
|
24
24
|
};
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
32
|
+
});
|
33
|
+
};
|
25
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
-
exports.getErrorMessage = exports.OAuthErrorResponse = exports.validateAbsoluteUrl = exports.relayStatePrefix = exports.IndexNames = void 0;
|
35
|
+
exports.generateJwkThumbprint = exports.exportPublicKeyJWK = exports.importJWTPublicKey = exports.isJWSKeyPairLoaded = exports.loadJWSPrivateKey = exports.getErrorMessage = exports.OAuthErrorResponse = exports.validateAbsoluteUrl = exports.relayStatePrefix = exports.IndexNames = void 0;
|
27
36
|
const error_1 = require("./error");
|
28
37
|
const redirect = __importStar(require("./oauth/redirect"));
|
38
|
+
const jose = __importStar(require("jose"));
|
29
39
|
var IndexNames;
|
30
40
|
(function (IndexNames) {
|
31
41
|
IndexNames["EntityID"] = "entityID";
|
@@ -53,3 +63,34 @@ function getErrorMessage(error) {
|
|
53
63
|
return String(error);
|
54
64
|
}
|
55
65
|
exports.getErrorMessage = getErrorMessage;
|
66
|
+
function loadJWSPrivateKey(key, alg) {
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
68
|
+
const pkcs8 = Buffer.from(key, 'base64').toString('ascii');
|
69
|
+
const privateKey = yield jose.importPKCS8(pkcs8, alg);
|
70
|
+
return privateKey;
|
71
|
+
});
|
72
|
+
}
|
73
|
+
exports.loadJWSPrivateKey = loadJWSPrivateKey;
|
74
|
+
function isJWSKeyPairLoaded(jwsKeyPair) {
|
75
|
+
if (!jwsKeyPair.private || !jwsKeyPair.public) {
|
76
|
+
return false;
|
77
|
+
}
|
78
|
+
return true;
|
79
|
+
}
|
80
|
+
exports.isJWSKeyPairLoaded = isJWSKeyPairLoaded;
|
81
|
+
const importJWTPublicKey = (key, jwsAlg) => __awaiter(void 0, void 0, void 0, function* () {
|
82
|
+
const spki = Buffer.from(key, 'base64').toString('ascii');
|
83
|
+
const publicKey = yield jose.importSPKI(spki, jwsAlg);
|
84
|
+
return publicKey;
|
85
|
+
});
|
86
|
+
exports.importJWTPublicKey = importJWTPublicKey;
|
87
|
+
const exportPublicKeyJWK = (key) => __awaiter(void 0, void 0, void 0, function* () {
|
88
|
+
const publicJWK = yield jose.exportJWK(key);
|
89
|
+
return publicJWK;
|
90
|
+
});
|
91
|
+
exports.exportPublicKeyJWK = exportPublicKeyJWK;
|
92
|
+
const generateJwkThumbprint = (jwk) => __awaiter(void 0, void 0, void 0, function* () {
|
93
|
+
const thumbprint = yield jose.calculateJwkThumbprint(jwk);
|
94
|
+
return thumbprint;
|
95
|
+
});
|
96
|
+
exports.generateJwkThumbprint = generateJwkThumbprint;
|
package/dist/db/sql/sql.js
CHANGED
@@ -42,11 +42,11 @@ class Sql {
|
|
42
42
|
}
|
43
43
|
init({ JacksonStore, JacksonIndex, JacksonTTL }) {
|
44
44
|
return __awaiter(this, void 0, void 0, function* () {
|
45
|
+
const sqlType = this.options.engine === 'planetscale' ? 'mysql' : this.options.type;
|
45
46
|
while (true) {
|
46
47
|
try {
|
47
48
|
this.dataSource = new typeorm_1.DataSource({
|
48
|
-
|
49
|
-
type: this.options.engine === 'planetscale' ? 'mysql' : this.options.type,
|
49
|
+
type: sqlType,
|
50
50
|
url: this.options.url,
|
51
51
|
synchronize: this.options.engine !== 'planetscale',
|
52
52
|
migrationsTableName: '_jackson_migrations',
|
@@ -58,7 +58,7 @@ class Sql {
|
|
58
58
|
break;
|
59
59
|
}
|
60
60
|
catch (err) {
|
61
|
-
console.error(`error connecting to ${this.options.type} db: ${err}`);
|
61
|
+
console.error(`error connecting to engine: ${this.options.engine}, type: ${sqlType} db: ${err}`);
|
62
62
|
yield dbutils.sleep(1000);
|
63
63
|
continue;
|
64
64
|
}
|
package/dist/index.d.ts
CHANGED
@@ -3,6 +3,7 @@ import { APIController } from './controller/api';
|
|
3
3
|
import { OAuthController } from './controller/oauth';
|
4
4
|
import { HealthCheckController } from './controller/health-check';
|
5
5
|
import { LogoutController } from './controller/logout';
|
6
|
+
import { OidcDiscoveryController } from './controller/oidc-discovery';
|
6
7
|
import { JacksonOption } from './typings';
|
7
8
|
export declare const controllers: (opts: JacksonOption) => Promise<{
|
8
9
|
apiController: APIController;
|
@@ -10,6 +11,7 @@ export declare const controllers: (opts: JacksonOption) => Promise<{
|
|
10
11
|
adminController: AdminController;
|
11
12
|
logoutController: LogoutController;
|
12
13
|
healthCheckController: HealthCheckController;
|
14
|
+
oidcDiscoveryController: OidcDiscoveryController;
|
13
15
|
}>;
|
14
16
|
export default controllers;
|
15
17
|
export * from './typings';
|
package/dist/index.js
CHANGED
@@ -32,6 +32,7 @@ const api_1 = require("./controller/api");
|
|
32
32
|
const oauth_1 = require("./controller/oauth");
|
33
33
|
const health_check_1 = require("./controller/health-check");
|
34
34
|
const logout_1 = require("./controller/logout");
|
35
|
+
const oidc_discovery_1 = require("./controller/oidc-discovery");
|
35
36
|
const db_1 = __importDefault(require("./db/db"));
|
36
37
|
const defaultDb_1 = __importDefault(require("./db/defaultDb"));
|
37
38
|
const read_config_1 = __importDefault(require("./read-config"));
|
@@ -49,6 +50,8 @@ const defaultOpts = (opts) => {
|
|
49
50
|
(0, defaultDb_1.default)(newOpts);
|
50
51
|
newOpts.clientSecretVerifier = newOpts.clientSecretVerifier || 'dummy';
|
51
52
|
newOpts.db.pageLimit = newOpts.db.pageLimit || 50;
|
53
|
+
newOpts.openid = newOpts.openid || {};
|
54
|
+
newOpts.openid.jwsAlg = newOpts.openid.jwsAlg || 'RS256';
|
52
55
|
return newOpts;
|
53
56
|
};
|
54
57
|
const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
@@ -75,6 +78,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
78
|
sessionStore,
|
76
79
|
opts,
|
77
80
|
});
|
81
|
+
const oidcDiscoveryController = new oidc_discovery_1.OidcDiscoveryController({ opts });
|
78
82
|
// write pre-loaded config if present
|
79
83
|
if (opts.preLoadedConfig && opts.preLoadedConfig.length > 0) {
|
80
84
|
const configs = yield (0, read_config_1.default)(opts.preLoadedConfig);
|
@@ -91,6 +95,7 @@ const controllers = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
95
|
adminController,
|
92
96
|
logoutController,
|
93
97
|
healthCheckController,
|
98
|
+
oidcDiscoveryController,
|
94
99
|
};
|
95
100
|
});
|
96
101
|
exports.controllers = controllers;
|
package/dist/typings.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { type JWK } from 'jose';
|
1
2
|
export declare type IdPConfig = {
|
2
3
|
defaultRedirectUrl: string;
|
3
4
|
redirectUrl: string[] | string;
|
@@ -44,6 +45,23 @@ export interface IHealthCheckController {
|
|
44
45
|
}>;
|
45
46
|
init(): Promise<void>;
|
46
47
|
}
|
48
|
+
export interface IOidcDiscoveryController {
|
49
|
+
openidConfig(): {
|
50
|
+
issuer: string;
|
51
|
+
authorization_endpoint: string;
|
52
|
+
token_endpoint: string;
|
53
|
+
userinfo_endpoint: string;
|
54
|
+
jwks_uri: string;
|
55
|
+
response_types_supported: Array<string>;
|
56
|
+
subject_types_supported: Array<string>;
|
57
|
+
id_token_signing_alg_values_supported: Array<string>;
|
58
|
+
grant_types_supported: Array<string>;
|
59
|
+
code_challenge_methods_supported: Array<string>;
|
60
|
+
};
|
61
|
+
jwks(): Promise<{
|
62
|
+
keys: JWK[];
|
63
|
+
}>;
|
64
|
+
}
|
47
65
|
export interface OAuthReqBody {
|
48
66
|
response_type: 'code';
|
49
67
|
client_id: string;
|
@@ -53,6 +71,7 @@ export interface OAuthReqBody {
|
|
53
71
|
product?: string;
|
54
72
|
access_type?: string;
|
55
73
|
scope?: string;
|
74
|
+
nonce?: string;
|
56
75
|
code_challenge: string;
|
57
76
|
code_challenge_method: 'plain' | 'S256' | '';
|
58
77
|
provider: 'saml';
|
@@ -72,11 +91,13 @@ export interface OAuthTokenReq {
|
|
72
91
|
}
|
73
92
|
export interface OAuthTokenRes {
|
74
93
|
access_token: string;
|
94
|
+
id_token?: string;
|
75
95
|
token_type: 'bearer';
|
76
96
|
expires_in: number;
|
77
97
|
}
|
78
98
|
export interface Profile {
|
79
99
|
id: string;
|
100
|
+
sub?: string;
|
80
101
|
email: string;
|
81
102
|
firstName: string;
|
82
103
|
lastName: string;
|
@@ -127,6 +148,13 @@ export interface JacksonOption {
|
|
127
148
|
db: DatabaseOption;
|
128
149
|
clientSecretVerifier?: string;
|
129
150
|
idpDiscoveryPath?: string;
|
151
|
+
openid: {
|
152
|
+
jwsAlg: string;
|
153
|
+
jwtSigningKeys?: {
|
154
|
+
private: string;
|
155
|
+
public: string;
|
156
|
+
};
|
157
|
+
};
|
130
158
|
}
|
131
159
|
export interface SLORequestParams {
|
132
160
|
nameId: string;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "1.0
|
3
|
+
"version": "1.1.0",
|
4
4
|
"description": "SAML Jackson library",
|
5
5
|
"keywords": [
|
6
6
|
"SAML 2.0"
|
@@ -38,12 +38,13 @@
|
|
38
38
|
"statements": 70
|
39
39
|
},
|
40
40
|
"dependencies": {
|
41
|
-
"@boxyhq/saml20": "1.0.
|
41
|
+
"@boxyhq/saml20": "1.0.4",
|
42
42
|
"@opentelemetry/api-metrics": "0.27.0",
|
43
43
|
"@opentelemetry/api": "1.0.4",
|
44
44
|
"@peculiar/webcrypto": "1.4.0",
|
45
|
-
"@peculiar/x509": "1.
|
46
|
-
"
|
45
|
+
"@peculiar/x509": "1.8.0",
|
46
|
+
"jose": "4.8.3",
|
47
|
+
"mongodb": "4.8.0",
|
47
48
|
"mysql2": "2.3.3",
|
48
49
|
"pg": "8.7.3",
|
49
50
|
"redis": "4.0.6",
|
@@ -54,18 +55,18 @@
|
|
54
55
|
"xmlbuilder": "15.1.1"
|
55
56
|
},
|
56
57
|
"devDependencies": {
|
57
|
-
"@types/node": "18.0.
|
58
|
-
"@types/sinon": "10.0.
|
58
|
+
"@types/node": "18.0.6",
|
59
|
+
"@types/sinon": "10.0.13",
|
59
60
|
"@types/tap": "15.0.7",
|
60
|
-
"@typescript-eslint/eslint-plugin": "5.30.
|
61
|
-
"@typescript-eslint/parser": "5.30.
|
61
|
+
"@typescript-eslint/eslint-plugin": "5.30.7",
|
62
|
+
"@typescript-eslint/parser": "5.30.7",
|
62
63
|
"cross-env": "7.0.3",
|
63
|
-
"eslint": "8.
|
64
|
+
"eslint": "8.20.0",
|
64
65
|
"eslint-config-prettier": "8.5.0",
|
65
66
|
"prettier": "2.7.1",
|
66
67
|
"sinon": "14.0.0",
|
67
68
|
"tap": "16.3.0",
|
68
|
-
"ts-node": "10.
|
69
|
+
"ts-node": "10.9.1",
|
69
70
|
"tsconfig-paths": "4.0.0",
|
70
71
|
"typescript": "4.7.4"
|
71
72
|
},
|