@boxyhq/saml-jackson 0.2.3-beta.235 → 0.2.3-beta.240
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.d.ts +32 -0
- package/dist/controller/api.js +193 -0
- package/dist/controller/error.d.ts +5 -0
- package/dist/controller/error.js +12 -0
- package/dist/controller/oauth/allowed.d.ts +1 -0
- package/dist/controller/oauth/allowed.js +17 -0
- package/dist/controller/oauth/code-verifier.d.ts +2 -0
- package/dist/controller/oauth/code-verifier.js +15 -0
- package/dist/controller/oauth/redirect.d.ts +1 -0
- package/dist/controller/oauth/redirect.js +11 -0
- package/dist/controller/oauth.d.ts +23 -0
- package/dist/controller/oauth.js +263 -0
- package/dist/controller/utils.d.ts +6 -0
- package/dist/controller/utils.js +17 -0
- package/dist/db/db.d.ts +15 -0
- package/dist/db/db.js +107 -0
- package/dist/db/encrypter.d.ts +3 -0
- package/dist/db/encrypter.js +29 -0
- package/dist/db/mem.d.ts +20 -0
- package/dist/db/mem.js +128 -0
- package/dist/db/mongo.d.ts +17 -0
- package/dist/db/mongo.js +106 -0
- package/dist/db/redis.d.ts +15 -0
- package/dist/db/redis.js +107 -0
- package/dist/db/sql/entity/JacksonIndex.d.ts +7 -0
- package/dist/db/sql/entity/JacksonIndex.js +41 -0
- package/dist/db/sql/entity/JacksonStore.d.ts +6 -0
- package/dist/db/sql/entity/JacksonStore.js +42 -0
- package/dist/db/sql/entity/JacksonTTL.d.ts +4 -0
- package/dist/db/sql/entity/JacksonTTL.js +29 -0
- package/dist/db/sql/sql.d.ts +20 -0
- package/dist/db/sql/sql.js +174 -0
- package/dist/db/store.d.ts +5 -0
- package/dist/db/store.js +68 -0
- package/dist/db/utils.d.ts +7 -0
- package/dist/db/utils.js +29 -0
- package/dist/env.d.ts +22 -0
- package/dist/env.js +35 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +80 -0
- package/dist/jackson.d.ts +1 -0
- package/dist/jackson.js +153 -0
- package/dist/read-config.d.ts +3 -0
- package/dist/read-config.js +50 -0
- package/dist/saml/claims.d.ts +6 -0
- package/dist/saml/claims.js +35 -0
- package/dist/saml/saml.d.ts +11 -0
- package/dist/saml/saml.js +200 -0
- package/dist/saml/x509.d.ts +7 -0
- package/dist/saml/x509.js +69 -0
- package/dist/typings.d.ts +137 -0
- package/dist/typings.js +2 -0
- 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,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,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,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 = {}));
|
package/dist/db/db.d.ts
ADDED
@@ -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;
|