@boxyhq/saml-jackson 0.2.3-beta.235 → 0.2.3-beta.240

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/dist/controller/api.d.ts +32 -0
  2. package/dist/controller/api.js +193 -0
  3. package/dist/controller/error.d.ts +5 -0
  4. package/dist/controller/error.js +12 -0
  5. package/dist/controller/oauth/allowed.d.ts +1 -0
  6. package/dist/controller/oauth/allowed.js +17 -0
  7. package/dist/controller/oauth/code-verifier.d.ts +2 -0
  8. package/dist/controller/oauth/code-verifier.js +15 -0
  9. package/dist/controller/oauth/redirect.d.ts +1 -0
  10. package/dist/controller/oauth/redirect.js +11 -0
  11. package/dist/controller/oauth.d.ts +23 -0
  12. package/dist/controller/oauth.js +263 -0
  13. package/dist/controller/utils.d.ts +6 -0
  14. package/dist/controller/utils.js +17 -0
  15. package/dist/db/db.d.ts +15 -0
  16. package/dist/db/db.js +107 -0
  17. package/dist/db/encrypter.d.ts +3 -0
  18. package/dist/db/encrypter.js +29 -0
  19. package/dist/db/mem.d.ts +20 -0
  20. package/dist/db/mem.js +128 -0
  21. package/dist/db/mongo.d.ts +17 -0
  22. package/dist/db/mongo.js +106 -0
  23. package/dist/db/redis.d.ts +15 -0
  24. package/dist/db/redis.js +107 -0
  25. package/dist/db/sql/entity/JacksonIndex.d.ts +7 -0
  26. package/dist/db/sql/entity/JacksonIndex.js +41 -0
  27. package/dist/db/sql/entity/JacksonStore.d.ts +6 -0
  28. package/dist/db/sql/entity/JacksonStore.js +42 -0
  29. package/dist/db/sql/entity/JacksonTTL.d.ts +4 -0
  30. package/dist/db/sql/entity/JacksonTTL.js +29 -0
  31. package/dist/db/sql/sql.d.ts +20 -0
  32. package/dist/db/sql/sql.js +174 -0
  33. package/dist/db/store.d.ts +5 -0
  34. package/dist/db/store.js +68 -0
  35. package/dist/db/utils.d.ts +7 -0
  36. package/dist/db/utils.js +29 -0
  37. package/dist/env.d.ts +22 -0
  38. package/dist/env.js +35 -0
  39. package/dist/index.d.ts +9 -0
  40. package/dist/index.js +80 -0
  41. package/dist/jackson.d.ts +1 -0
  42. package/dist/jackson.js +153 -0
  43. package/dist/read-config.d.ts +3 -0
  44. package/dist/read-config.js +50 -0
  45. package/dist/saml/claims.d.ts +6 -0
  46. package/dist/saml/claims.js +35 -0
  47. package/dist/saml/saml.d.ts +11 -0
  48. package/dist/saml/saml.js +200 -0
  49. package/dist/saml/x509.d.ts +7 -0
  50. package/dist/saml/x509.js +69 -0
  51. package/dist/typings.d.ts +137 -0
  52. package/dist/typings.js +2 -0
  53. package/package.json +2 -1
@@ -0,0 +1,32 @@
1
+ import { IdPConfig, ISAMLConfig, OAuth } from '../typings';
2
+ export declare class SAMLConfig implements ISAMLConfig {
3
+ private configStore;
4
+ constructor({ configStore }: {
5
+ configStore: any;
6
+ });
7
+ private _validateIdPConfig;
8
+ create(body: IdPConfig): Promise<OAuth>;
9
+ get(body: {
10
+ clientID: string;
11
+ tenant: string;
12
+ product: string;
13
+ }): Promise<Partial<OAuth>>;
14
+ delete(body: {
15
+ clientID: string;
16
+ clientSecret: string;
17
+ tenant: string;
18
+ product: string;
19
+ }): Promise<void>;
20
+ config(body: IdPConfig): Promise<OAuth>;
21
+ getConfig(body: {
22
+ clientID: string;
23
+ tenant: string;
24
+ product: string;
25
+ }): Promise<Partial<OAuth>>;
26
+ deleteConfig(body: {
27
+ clientID: string;
28
+ clientSecret: string;
29
+ tenant: string;
30
+ product: string;
31
+ }): Promise<void>;
32
+ }
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ var __importDefault = (this && this.__importDefault) || function (mod) {
31
+ return (mod && mod.__esModule) ? mod : { "default": mod };
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.SAMLConfig = void 0;
35
+ const crypto_1 = __importDefault(require("crypto"));
36
+ const dbutils = __importStar(require("../db/utils"));
37
+ const saml_1 = __importDefault(require("../saml/saml"));
38
+ const error_1 = require("./error");
39
+ const utils_1 = require("./utils");
40
+ const x509_1 = __importDefault(require("../saml/x509"));
41
+ class SAMLConfig {
42
+ constructor({ configStore }) {
43
+ this.configStore = configStore;
44
+ }
45
+ _validateIdPConfig(body) {
46
+ const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
47
+ if (!rawMetadata) {
48
+ throw new error_1.JacksonError('Please provide rawMetadata', 400);
49
+ }
50
+ if (!defaultRedirectUrl) {
51
+ throw new error_1.JacksonError('Please provide a defaultRedirectUrl', 400);
52
+ }
53
+ if (!redirectUrl) {
54
+ throw new error_1.JacksonError('Please provide redirectUrl', 400);
55
+ }
56
+ if (!tenant) {
57
+ throw new error_1.JacksonError('Please provide tenant', 400);
58
+ }
59
+ if (!product) {
60
+ throw new error_1.JacksonError('Please provide product', 400);
61
+ }
62
+ }
63
+ create(body) {
64
+ return __awaiter(this, void 0, void 0, function* () {
65
+ const { rawMetadata, defaultRedirectUrl, redirectUrl, tenant, product } = body;
66
+ this._validateIdPConfig(body);
67
+ const idpMetadata = yield saml_1.default.parseMetadataAsync(rawMetadata);
68
+ // extract provider
69
+ let providerName = extractHostName(idpMetadata.entityID);
70
+ if (!providerName) {
71
+ providerName = extractHostName(idpMetadata.sso.redirectUrl || idpMetadata.sso.postUrl);
72
+ }
73
+ idpMetadata.provider = providerName ? providerName : 'Unknown';
74
+ const clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, idpMetadata.entityID));
75
+ let clientSecret;
76
+ const exists = yield this.configStore.get(clientID);
77
+ if (exists) {
78
+ clientSecret = exists.clientSecret;
79
+ }
80
+ else {
81
+ clientSecret = crypto_1.default.randomBytes(24).toString('hex');
82
+ }
83
+ const certs = yield x509_1.default.generate();
84
+ if (!certs) {
85
+ throw new Error('Error generating x59 certs');
86
+ }
87
+ yield this.configStore.put(clientID, {
88
+ idpMetadata,
89
+ defaultRedirectUrl,
90
+ redirectUrl: JSON.parse(redirectUrl),
91
+ tenant,
92
+ product,
93
+ clientID,
94
+ clientSecret,
95
+ certs,
96
+ }, {
97
+ // secondary index on entityID
98
+ name: utils_1.IndexNames.EntityID,
99
+ value: idpMetadata.entityID,
100
+ }, {
101
+ // secondary index on tenant + product
102
+ name: utils_1.IndexNames.TenantProduct,
103
+ value: dbutils.keyFromParts(tenant, product),
104
+ });
105
+ return {
106
+ client_id: clientID,
107
+ client_secret: clientSecret,
108
+ provider: idpMetadata.provider,
109
+ };
110
+ });
111
+ }
112
+ get(body) {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ const { clientID, tenant, product } = body;
115
+ if (clientID) {
116
+ const samlConfig = yield this.configStore.get(clientID);
117
+ return samlConfig ? { provider: samlConfig.idpMetadata.provider } : {};
118
+ }
119
+ if (tenant && product) {
120
+ const samlConfigs = yield this.configStore.getByIndex({
121
+ name: utils_1.IndexNames.TenantProduct,
122
+ value: dbutils.keyFromParts(tenant, product),
123
+ });
124
+ if (!samlConfigs || !samlConfigs.length) {
125
+ return {};
126
+ }
127
+ return { provider: samlConfigs[0].idpMetadata.provider };
128
+ }
129
+ throw new error_1.JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
130
+ });
131
+ }
132
+ delete(body) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const { clientID, clientSecret, tenant, product } = body;
135
+ if (clientID && clientSecret) {
136
+ const samlConfig = yield this.configStore.get(clientID);
137
+ if (!samlConfig) {
138
+ return;
139
+ }
140
+ if (samlConfig.clientSecret === clientSecret) {
141
+ yield this.configStore.delete(clientID);
142
+ }
143
+ else {
144
+ throw new error_1.JacksonError('clientSecret mismatch.', 400);
145
+ }
146
+ return;
147
+ }
148
+ if (tenant && product) {
149
+ const samlConfigs = yield this.configStore.getByIndex({
150
+ name: utils_1.IndexNames.TenantProduct,
151
+ value: dbutils.keyFromParts(tenant, product),
152
+ });
153
+ if (!samlConfigs || !samlConfigs.length) {
154
+ return;
155
+ }
156
+ for (const conf of samlConfigs) {
157
+ yield this.configStore.delete(conf.clientID);
158
+ }
159
+ return;
160
+ }
161
+ throw new error_1.JacksonError('Please provide `clientID` and `clientSecret` or `tenant` and `product`.', 400);
162
+ });
163
+ }
164
+ // Ensure backward compatibility
165
+ config(body) {
166
+ return __awaiter(this, void 0, void 0, function* () {
167
+ return this.create(body);
168
+ });
169
+ }
170
+ getConfig(body) {
171
+ return __awaiter(this, void 0, void 0, function* () {
172
+ return this.get(body);
173
+ });
174
+ }
175
+ deleteConfig(body) {
176
+ return __awaiter(this, void 0, void 0, function* () {
177
+ return this.delete(body);
178
+ });
179
+ }
180
+ }
181
+ exports.SAMLConfig = SAMLConfig;
182
+ const extractHostName = (url) => {
183
+ try {
184
+ const pUrl = new URL(url);
185
+ if (pUrl.hostname.startsWith('www.')) {
186
+ return pUrl.hostname.substring(4);
187
+ }
188
+ return pUrl.hostname;
189
+ }
190
+ catch (err) {
191
+ return null;
192
+ }
193
+ };
@@ -0,0 +1,5 @@
1
+ export declare class JacksonError extends Error {
2
+ name: string;
3
+ statusCode: number;
4
+ constructor(message: string, statusCode?: number);
5
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JacksonError = void 0;
4
+ class JacksonError extends Error {
5
+ constructor(message, statusCode = 500) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ this.statusCode = statusCode;
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+ exports.JacksonError = JacksonError;
@@ -0,0 +1 @@
1
+ export declare const redirect: (redirectUrl: string, redirectUrls: string[]) => boolean;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.redirect = void 0;
4
+ const redirect = (redirectUrl, redirectUrls) => {
5
+ const url = new URL(redirectUrl);
6
+ for (const idx in redirectUrls) {
7
+ const rUrl = new URL(redirectUrls[idx]);
8
+ // TODO: Check pathname, for now pathname is ignored
9
+ if (rUrl.protocol === url.protocol &&
10
+ rUrl.hostname === url.hostname &&
11
+ rUrl.port === url.port) {
12
+ return true;
13
+ }
14
+ }
15
+ return false;
16
+ };
17
+ exports.redirect = redirect;
@@ -0,0 +1,2 @@
1
+ export declare const transformBase64: (input: string) => string;
2
+ export declare const encode: (code_challenge: string) => string;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encode = exports.transformBase64 = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const transformBase64 = (input) => {
9
+ return input.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
10
+ };
11
+ exports.transformBase64 = transformBase64;
12
+ const encode = (code_challenge) => {
13
+ return (0, exports.transformBase64)(crypto_1.default.createHash('sha256').update(code_challenge).digest('base64'));
14
+ };
15
+ exports.encode = encode;
@@ -0,0 +1 @@
1
+ export declare const success: (redirectUrl: string, params: Record<string, string>) => string;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.success = void 0;
4
+ const success = (redirectUrl, params) => {
5
+ const url = new URL(redirectUrl);
6
+ for (const [key, value] of Object.entries(params)) {
7
+ url.searchParams.set(key, value);
8
+ }
9
+ return url.href;
10
+ };
11
+ exports.success = success;
@@ -0,0 +1,23 @@
1
+ import { IOAuthController, OAuthReqBody, OAuthTokenReq, OAuthTokenRes, Profile, SAMLResponsePayload } from '../typings';
2
+ export declare class OAuthController implements IOAuthController {
3
+ private configStore;
4
+ private sessionStore;
5
+ private codeStore;
6
+ private tokenStore;
7
+ private opts;
8
+ constructor({ configStore, sessionStore, codeStore, tokenStore, opts }: {
9
+ configStore: any;
10
+ sessionStore: any;
11
+ codeStore: any;
12
+ tokenStore: any;
13
+ opts: any;
14
+ });
15
+ authorize(body: OAuthReqBody): Promise<{
16
+ redirect_url: string;
17
+ }>;
18
+ samlResponse(body: SAMLResponsePayload): Promise<{
19
+ redirect_url: string;
20
+ }>;
21
+ token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
22
+ userInfo(token: string): Promise<Profile>;
23
+ }
@@ -0,0 +1,263 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ var __importDefault = (this && this.__importDefault) || function (mod) {
31
+ return (mod && mod.__esModule) ? mod : { "default": mod };
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.OAuthController = void 0;
35
+ const crypto_1 = __importDefault(require("crypto"));
36
+ const dbutils = __importStar(require("../db/utils"));
37
+ const saml_1 = __importDefault(require("../saml/saml"));
38
+ const error_1 = require("./error");
39
+ const allowed = __importStar(require("./oauth/allowed"));
40
+ const codeVerifier = __importStar(require("./oauth/code-verifier"));
41
+ const redirect = __importStar(require("./oauth/redirect"));
42
+ const utils_1 = require("./utils");
43
+ const relayStatePrefix = 'boxyhq_jackson_';
44
+ function getEncodedClientId(client_id) {
45
+ try {
46
+ const sp = new URLSearchParams(client_id);
47
+ const tenant = sp.get('tenant');
48
+ const product = sp.get('product');
49
+ if (tenant && product) {
50
+ return {
51
+ tenant: sp.get('tenant'),
52
+ product: sp.get('product'),
53
+ };
54
+ }
55
+ return null;
56
+ }
57
+ catch (err) {
58
+ return null;
59
+ }
60
+ }
61
+ class OAuthController {
62
+ constructor({ configStore, sessionStore, codeStore, tokenStore, opts }) {
63
+ this.configStore = configStore;
64
+ this.sessionStore = sessionStore;
65
+ this.codeStore = codeStore;
66
+ this.tokenStore = tokenStore;
67
+ this.opts = opts;
68
+ }
69
+ authorize(body) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ const { response_type = 'code', client_id, redirect_uri, state, tenant, product, code_challenge, code_challenge_method = '',
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
+ provider = 'saml', } = body;
74
+ if (!redirect_uri) {
75
+ throw new error_1.JacksonError('Please specify a redirect URL.', 400);
76
+ }
77
+ if (!state) {
78
+ throw new error_1.JacksonError('Please specify a state to safeguard against XSRF attacks.', 400);
79
+ }
80
+ let samlConfig;
81
+ if (tenant && product) {
82
+ const samlConfigs = yield this.configStore.getByIndex({
83
+ name: utils_1.IndexNames.TenantProduct,
84
+ value: dbutils.keyFromParts(tenant, product),
85
+ });
86
+ if (!samlConfigs || samlConfigs.length === 0) {
87
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
88
+ }
89
+ // TODO: Support multiple matches
90
+ samlConfig = samlConfigs[0];
91
+ }
92
+ else if (client_id &&
93
+ client_id !== '' &&
94
+ client_id !== 'undefined' &&
95
+ client_id !== 'null') {
96
+ // if tenant and product are encoded in the client_id then we parse it and check for the relevant config(s)
97
+ const sp = getEncodedClientId(client_id);
98
+ if (sp === null || sp === void 0 ? void 0 : sp.tenant) {
99
+ const samlConfigs = yield this.configStore.getByIndex({
100
+ name: utils_1.IndexNames.TenantProduct,
101
+ value: dbutils.keyFromParts(sp.tenant, sp.product || ''),
102
+ });
103
+ if (!samlConfigs || samlConfigs.length === 0) {
104
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
105
+ }
106
+ // TODO: Support multiple matches
107
+ samlConfig = samlConfigs[0];
108
+ }
109
+ else {
110
+ samlConfig = yield this.configStore.get(client_id);
111
+ }
112
+ }
113
+ else {
114
+ throw new error_1.JacksonError('You need to specify client_id or tenant & product', 403);
115
+ }
116
+ if (!samlConfig) {
117
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
118
+ }
119
+ if (!allowed.redirect(redirect_uri, samlConfig.redirectUrl)) {
120
+ throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
121
+ }
122
+ const samlReq = saml_1.default.request({
123
+ entityID: this.opts.samlAudience,
124
+ callbackUrl: this.opts.externalUrl + this.opts.samlPath,
125
+ signingKey: samlConfig.certs.privateKey,
126
+ });
127
+ const sessionId = crypto_1.default.randomBytes(16).toString('hex');
128
+ yield this.sessionStore.put(sessionId, {
129
+ id: samlReq.id,
130
+ redirect_uri,
131
+ response_type,
132
+ state,
133
+ code_challenge,
134
+ code_challenge_method,
135
+ });
136
+ const redirectUrl = redirect.success(samlConfig.idpMetadata.sso.redirectUrl, {
137
+ RelayState: relayStatePrefix + sessionId,
138
+ SAMLRequest: Buffer.from(samlReq.request).toString('base64'),
139
+ });
140
+ return { redirect_url: redirectUrl };
141
+ });
142
+ }
143
+ samlResponse(body) {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ const { SAMLResponse } = body; // RelayState will contain the sessionId from earlier quasi-oauth flow
146
+ let RelayState = body.RelayState || '';
147
+ if (!this.opts.idpEnabled && !RelayState.startsWith(relayStatePrefix)) {
148
+ // IDP is disabled so block the request
149
+ throw new error_1.JacksonError('IdP (Identity Provider) flow has been disabled. Please head to your Service Provider to login.', 403);
150
+ }
151
+ if (!RelayState.startsWith(relayStatePrefix)) {
152
+ RelayState = '';
153
+ }
154
+ RelayState = RelayState.replace(relayStatePrefix, '');
155
+ const rawResponse = Buffer.from(SAMLResponse, 'base64').toString();
156
+ const parsedResp = yield saml_1.default.parseAsync(rawResponse);
157
+ const samlConfigs = yield this.configStore.getByIndex({
158
+ name: utils_1.IndexNames.EntityID,
159
+ value: parsedResp === null || parsedResp === void 0 ? void 0 : parsedResp.issuer,
160
+ });
161
+ if (!samlConfigs || samlConfigs.length === 0) {
162
+ throw new error_1.JacksonError('SAML configuration not found.', 403);
163
+ }
164
+ // TODO: Support multiple matches
165
+ const samlConfig = samlConfigs[0];
166
+ let session;
167
+ if (RelayState !== '') {
168
+ session = yield this.sessionStore.get(RelayState);
169
+ if (!session) {
170
+ throw new error_1.JacksonError('Unable to validate state from the origin request.', 403);
171
+ }
172
+ }
173
+ const validateOpts = {
174
+ thumbprint: samlConfig.idpMetadata.thumbprint,
175
+ audience: this.opts.samlAudience,
176
+ };
177
+ if (session && session.id) {
178
+ validateOpts.inResponseTo = session.id;
179
+ }
180
+ const profile = yield saml_1.default.validateAsync(rawResponse, validateOpts);
181
+ // store details against a code
182
+ const code = crypto_1.default.randomBytes(20).toString('hex');
183
+ const codeVal = {
184
+ profile,
185
+ clientID: samlConfig.clientID,
186
+ clientSecret: samlConfig.clientSecret,
187
+ };
188
+ if (session) {
189
+ codeVal.session = session;
190
+ }
191
+ yield this.codeStore.put(code, codeVal);
192
+ if (session &&
193
+ session.redirect_uri &&
194
+ !allowed.redirect(session.redirect_uri, samlConfig.redirectUrl)) {
195
+ throw new error_1.JacksonError('Redirect URL is not allowed.', 403);
196
+ }
197
+ const params = {
198
+ code,
199
+ };
200
+ if (session && session.state) {
201
+ params.state = session.state;
202
+ }
203
+ const redirectUrl = redirect.success((session && session.redirect_uri) || samlConfig.defaultRedirectUrl, params);
204
+ return { redirect_url: redirectUrl };
205
+ });
206
+ }
207
+ token(body) {
208
+ return __awaiter(this, void 0, void 0, function* () {
209
+ const { client_id, client_secret, code_verifier, code, grant_type = 'authorization_code', } = body;
210
+ if (grant_type !== 'authorization_code') {
211
+ throw new error_1.JacksonError('Unsupported grant_type', 400);
212
+ }
213
+ if (!code) {
214
+ throw new error_1.JacksonError('Please specify code', 400);
215
+ }
216
+ const codeVal = yield this.codeStore.get(code);
217
+ if (!codeVal || !codeVal.profile) {
218
+ throw new error_1.JacksonError('Invalid code', 403);
219
+ }
220
+ if (client_id && client_secret) {
221
+ // check if we have an encoded client_id
222
+ if (client_id !== 'dummy' && client_secret !== 'dummy') {
223
+ const sp = getEncodedClientId(client_id);
224
+ if (!sp) {
225
+ // OAuth flow
226
+ if (client_id !== codeVal.clientID ||
227
+ client_secret !== codeVal.clientSecret) {
228
+ throw new error_1.JacksonError('Invalid client_id or client_secret', 401);
229
+ }
230
+ }
231
+ }
232
+ }
233
+ else if (code_verifier) {
234
+ // PKCE flow
235
+ let cv = code_verifier;
236
+ if (codeVal.session.code_challenge_method.toLowerCase() === 's256') {
237
+ cv = codeVerifier.encode(code_verifier);
238
+ }
239
+ if (codeVal.session.code_challenge !== cv) {
240
+ throw new error_1.JacksonError('Invalid code_verifier', 401);
241
+ }
242
+ }
243
+ else if (codeVal && codeVal.session) {
244
+ throw new error_1.JacksonError('Please specify client_secret or code_verifier', 401);
245
+ }
246
+ // store details against a token
247
+ const token = crypto_1.default.randomBytes(20).toString('hex');
248
+ yield this.tokenStore.put(token, codeVal.profile);
249
+ return {
250
+ access_token: token,
251
+ token_type: 'bearer',
252
+ expires_in: this.opts.db.ttl,
253
+ };
254
+ });
255
+ }
256
+ userInfo(token) {
257
+ return __awaiter(this, void 0, void 0, function* () {
258
+ const { claims } = yield this.tokenStore.get(token);
259
+ return claims;
260
+ });
261
+ }
262
+ }
263
+ exports.OAuthController = OAuthController;
@@ -0,0 +1,6 @@
1
+ import { Request } from 'express';
2
+ export declare const extractAuthToken: (req: Request) => string | null;
3
+ export declare enum IndexNames {
4
+ EntityID = "entityID",
5
+ TenantProduct = "tenantProduct"
6
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IndexNames = exports.extractAuthToken = void 0;
4
+ const extractAuthToken = (req) => {
5
+ const authHeader = req.get('authorization');
6
+ const parts = (authHeader || '').split(' ');
7
+ if (parts.length > 1) {
8
+ return parts[1];
9
+ }
10
+ return null;
11
+ };
12
+ exports.extractAuthToken = extractAuthToken;
13
+ var IndexNames;
14
+ (function (IndexNames) {
15
+ IndexNames["EntityID"] = "entityID";
16
+ IndexNames["TenantProduct"] = "tenantProduct";
17
+ })(IndexNames = exports.IndexNames || (exports.IndexNames = {}));
@@ -0,0 +1,15 @@
1
+ import { DatabaseDriver, DatabaseOption, EncryptionKey, Index, Storable } from '../typings';
2
+ declare class DB implements DatabaseDriver {
3
+ private db;
4
+ private encryptionKey;
5
+ constructor(db: DatabaseDriver, encryptionKey: EncryptionKey);
6
+ get(namespace: string, key: string): Promise<unknown>;
7
+ getByIndex(namespace: string, idx: Index): Promise<unknown[]>;
8
+ put(namespace: string, key: string, val: unknown, ttl?: number, ...indexes: Index[]): Promise<unknown>;
9
+ delete(namespace: string, key: string): Promise<unknown>;
10
+ store(namespace: string, ttl?: number): Storable;
11
+ }
12
+ declare const _default: {
13
+ new: (options: DatabaseOption) => Promise<DB>;
14
+ };
15
+ export = _default;