@boxyhq/saml-jackson 0.2.3-beta.226 → 0.2.3-beta.231
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/.eslintrc.js +16 -11
- package/package.json +6 -3
- package/src/controller/api.ts +2 -2
- package/src/controller/error.ts +1 -1
- package/src/controller/oauth.ts +4 -7
- package/src/db/db.ts +11 -11
- package/src/db/mem.ts +2 -2
- package/src/db/mongo.ts +2 -2
- package/src/db/redis.ts +3 -3
- package/src/db/sql/entity/JacksonIndex.ts +0 -1
- package/src/db/sql/sql.ts +9 -8
- package/src/db/store.ts +2 -2
- package/src/env.ts +1 -1
- package/src/index.ts +8 -3
- package/src/jackson.ts +8 -6
- package/src/saml/saml.ts +8 -9
- package/src/test/api.test.ts +1 -1
- package/src/test/oauth.test.ts +36 -27
- package/src/typings.ts +3 -3
package/.eslintrc.js
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module.exports = {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
2
|
+
env: {
|
3
|
+
es2021: true,
|
4
|
+
node: true,
|
5
|
+
},
|
6
|
+
parserOptions: {
|
7
|
+
ecmaVersion: 13,
|
8
|
+
sourceType: 'module',
|
9
|
+
},
|
10
|
+
root: true,
|
11
|
+
parser: '@typescript-eslint/parser',
|
12
|
+
plugins: ['@typescript-eslint'],
|
13
|
+
extends: [
|
14
|
+
'eslint:recommended',
|
15
|
+
'plugin:@typescript-eslint/recommended',
|
16
|
+
'prettier',
|
17
|
+
],
|
13
18
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@boxyhq/saml-jackson",
|
3
|
-
"version": "0.2.3-beta.
|
3
|
+
"version": "0.2.3-beta.231",
|
4
4
|
"license": "Apache 2.0",
|
5
5
|
"description": "SAML 2.0 service",
|
6
6
|
"main": "dist/index.js",
|
@@ -57,8 +57,11 @@
|
|
57
57
|
"@types/redis": "4.0.11",
|
58
58
|
"@types/sinon": "10.0.6",
|
59
59
|
"@types/tap": "15.0.5",
|
60
|
+
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
61
|
+
"@typescript-eslint/parser": "^5.8.1",
|
60
62
|
"cross-env": "7.0.3",
|
61
|
-
"eslint": "8.5.0",
|
63
|
+
"eslint": "^8.5.0",
|
64
|
+
"eslint-config-prettier": "^8.3.0",
|
62
65
|
"husky": "7.0.4",
|
63
66
|
"lint-staged": "12.1.4",
|
64
67
|
"nodemon": "2.0.15",
|
@@ -66,7 +69,7 @@
|
|
66
69
|
"sinon": "12.0.1",
|
67
70
|
"tap": "15.1.5",
|
68
71
|
"ts-node": "10.4.0",
|
69
|
-
"typescript": "4.5.4"
|
72
|
+
"typescript": "^4.5.4"
|
70
73
|
},
|
71
74
|
"lint-staged": {
|
72
75
|
"*.{js,ts}": "eslint --cache --fix",
|
package/src/controller/api.ts
CHANGED
@@ -56,13 +56,13 @@ export class SAMLConfig implements ISAMLConfig {
|
|
56
56
|
|
57
57
|
idpMetadata.provider = providerName ? providerName : 'Unknown';
|
58
58
|
|
59
|
-
|
59
|
+
const clientID = dbutils.keyDigest(
|
60
60
|
dbutils.keyFromParts(tenant, product, idpMetadata.entityID)
|
61
61
|
);
|
62
62
|
|
63
63
|
let clientSecret;
|
64
64
|
|
65
|
-
|
65
|
+
const exists = await this.configStore.get(clientID);
|
66
66
|
|
67
67
|
if (exists) {
|
68
68
|
clientSecret = exists.clientSecret;
|
package/src/controller/error.ts
CHANGED
package/src/controller/oauth.ts
CHANGED
@@ -66,7 +66,7 @@ export class OAuthController implements IOAuthController {
|
|
66
66
|
product,
|
67
67
|
code_challenge,
|
68
68
|
code_challenge_method = '',
|
69
|
-
// eslint-disable-next-line no-unused-vars
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
70
70
|
provider = 'saml',
|
71
71
|
} = body;
|
72
72
|
|
@@ -134,7 +134,6 @@ export class OAuthController implements IOAuthController {
|
|
134
134
|
}
|
135
135
|
|
136
136
|
const samlReq = saml.request({
|
137
|
-
// @ts-ignore
|
138
137
|
entityID: this.opts.samlAudience,
|
139
138
|
callbackUrl: this.opts.externalUrl + this.opts.samlPath,
|
140
139
|
signingKey: samlConfig.certs.privateKey,
|
@@ -190,8 +189,6 @@ export class OAuthController implements IOAuthController {
|
|
190
189
|
|
191
190
|
const samlConfigs = await this.configStore.getByIndex({
|
192
191
|
name: IndexNames.EntityID,
|
193
|
-
|
194
|
-
// @ts-ignore
|
195
192
|
value: parsedResp?.issuer,
|
196
193
|
});
|
197
194
|
|
@@ -214,7 +211,7 @@ export class OAuthController implements IOAuthController {
|
|
214
211
|
}
|
215
212
|
}
|
216
213
|
|
217
|
-
|
214
|
+
const validateOpts: Record<string, string> = {
|
218
215
|
thumbprint: samlConfig.idpMetadata.thumbprint,
|
219
216
|
audience: this.opts.samlAudience,
|
220
217
|
};
|
@@ -228,7 +225,7 @@ export class OAuthController implements IOAuthController {
|
|
228
225
|
// store details against a code
|
229
226
|
const code = crypto.randomBytes(20).toString('hex');
|
230
227
|
|
231
|
-
|
228
|
+
const codeVal: Record<string, unknown> = {
|
232
229
|
profile,
|
233
230
|
clientID: samlConfig.clientID,
|
234
231
|
clientSecret: samlConfig.clientSecret,
|
@@ -248,7 +245,7 @@ export class OAuthController implements IOAuthController {
|
|
248
245
|
throw new JacksonError('Redirect URL is not allowed.', 403);
|
249
246
|
}
|
250
247
|
|
251
|
-
|
248
|
+
const params: Record<string, string> = {
|
252
249
|
code,
|
253
250
|
};
|
254
251
|
|
package/src/db/db.ts
CHANGED
@@ -13,7 +13,7 @@ import redis from './redis';
|
|
13
13
|
import sql from './sql/sql';
|
14
14
|
import store from './store';
|
15
15
|
|
16
|
-
const decrypt = (res: Encrypted, encryptionKey: EncryptionKey):
|
16
|
+
const decrypt = (res: Encrypted, encryptionKey: EncryptionKey): unknown => {
|
17
17
|
if (res.iv && res.tag) {
|
18
18
|
return JSON.parse(
|
19
19
|
encrypter.decrypt(res.value, res.iv, res.tag, encryptionKey)
|
@@ -24,15 +24,15 @@ const decrypt = (res: Encrypted, encryptionKey: EncryptionKey): any => {
|
|
24
24
|
};
|
25
25
|
|
26
26
|
class DB implements DatabaseDriver {
|
27
|
-
private db:
|
27
|
+
private db: DatabaseDriver;
|
28
28
|
private encryptionKey: EncryptionKey;
|
29
29
|
|
30
|
-
constructor(db:
|
30
|
+
constructor(db: DatabaseDriver, encryptionKey: EncryptionKey) {
|
31
31
|
this.db = db;
|
32
32
|
this.encryptionKey = encryptionKey;
|
33
33
|
}
|
34
34
|
|
35
|
-
async get(namespace: string, key: string): Promise<
|
35
|
+
async get(namespace: string, key: string): Promise<unknown> {
|
36
36
|
const res = await this.db.get(namespace, key);
|
37
37
|
|
38
38
|
if (!res) {
|
@@ -42,7 +42,7 @@ class DB implements DatabaseDriver {
|
|
42
42
|
return decrypt(res, this.encryptionKey);
|
43
43
|
}
|
44
44
|
|
45
|
-
async getByIndex(namespace: string, idx: Index): Promise<
|
45
|
+
async getByIndex(namespace: string, idx: Index): Promise<unknown[]> {
|
46
46
|
const res = await this.db.getByIndex(namespace, idx);
|
47
47
|
const encryptionKey = this.encryptionKey;
|
48
48
|
return res.map((r) => {
|
@@ -54,10 +54,10 @@ class DB implements DatabaseDriver {
|
|
54
54
|
async put(
|
55
55
|
namespace: string,
|
56
56
|
key: string,
|
57
|
-
val:
|
58
|
-
ttl
|
59
|
-
...indexes:
|
60
|
-
): Promise<
|
57
|
+
val: unknown,
|
58
|
+
ttl = 0,
|
59
|
+
...indexes: Index[]
|
60
|
+
): Promise<unknown> {
|
61
61
|
if (ttl > 0 && indexes && indexes.length > 0) {
|
62
62
|
throw new Error('secondary indexes not allow on a store with ttl');
|
63
63
|
}
|
@@ -69,11 +69,11 @@ class DB implements DatabaseDriver {
|
|
69
69
|
return await this.db.put(namespace, key, dbVal, ttl, ...indexes);
|
70
70
|
}
|
71
71
|
|
72
|
-
async delete(namespace: string, key: string): Promise<
|
72
|
+
async delete(namespace: string, key: string): Promise<unknown> {
|
73
73
|
return await this.db.delete(namespace, key);
|
74
74
|
}
|
75
75
|
|
76
|
-
store(namespace: string, ttl
|
76
|
+
store(namespace: string, ttl = 0): Storable {
|
77
77
|
return store.new(namespace, this, ttl);
|
78
78
|
}
|
79
79
|
}
|
package/src/db/mem.ts
CHANGED
@@ -43,7 +43,7 @@ class Mem implements DatabaseDriver {
|
|
43
43
|
}
|
44
44
|
|
45
45
|
async get(namespace: string, key: string): Promise<any> {
|
46
|
-
|
46
|
+
const res = this.store[dbutils.key(namespace, key)];
|
47
47
|
if (res) {
|
48
48
|
return res;
|
49
49
|
}
|
@@ -66,7 +66,7 @@ class Mem implements DatabaseDriver {
|
|
66
66
|
namespace: string,
|
67
67
|
key: string,
|
68
68
|
val: Encrypted,
|
69
|
-
ttl
|
69
|
+
ttl = 0,
|
70
70
|
...indexes: any[]
|
71
71
|
): Promise<any> {
|
72
72
|
const k = dbutils.key(namespace, key);
|
package/src/db/mongo.ts
CHANGED
@@ -36,7 +36,7 @@ class Mongo implements DatabaseDriver {
|
|
36
36
|
}
|
37
37
|
|
38
38
|
async get(namespace: string, key: string): Promise<any> {
|
39
|
-
|
39
|
+
const res = await this.collection.findOne({
|
40
40
|
_id: dbutils.key(namespace, key),
|
41
41
|
});
|
42
42
|
if (res && res.value) {
|
@@ -65,7 +65,7 @@ class Mongo implements DatabaseDriver {
|
|
65
65
|
namespace: string,
|
66
66
|
key: string,
|
67
67
|
val: Encrypted,
|
68
|
-
ttl
|
68
|
+
ttl = 0,
|
69
69
|
...indexes: any[]
|
70
70
|
): Promise<void> {
|
71
71
|
const doc = <Document>{
|
package/src/db/redis.ts
CHANGED
@@ -11,7 +11,7 @@ class Redis implements DatabaseDriver {
|
|
11
11
|
}
|
12
12
|
|
13
13
|
async init(): Promise<Redis> {
|
14
|
-
|
14
|
+
const opts = {};
|
15
15
|
|
16
16
|
if (this.options && this.options.url) {
|
17
17
|
opts['socket'] = {
|
@@ -30,7 +30,7 @@ class Redis implements DatabaseDriver {
|
|
30
30
|
}
|
31
31
|
|
32
32
|
async get(namespace: string, key: string): Promise<any> {
|
33
|
-
|
33
|
+
const res = await this.client.get(dbutils.key(namespace, key));
|
34
34
|
if (res) {
|
35
35
|
return JSON.parse(res);
|
36
36
|
}
|
@@ -55,7 +55,7 @@ class Redis implements DatabaseDriver {
|
|
55
55
|
namespace: string,
|
56
56
|
key: string,
|
57
57
|
val: Encrypted,
|
58
|
-
ttl
|
58
|
+
ttl = 0,
|
59
59
|
...indexes: any[]
|
60
60
|
): Promise<void> {
|
61
61
|
let tx = this.client.multi();
|
package/src/db/sql/sql.ts
CHANGED
@@ -5,20 +5,21 @@ require('reflect-metadata');
|
|
5
5
|
import { DatabaseDriver, DatabaseOption, Index, Encrypted } from 'saml-jackson';
|
6
6
|
import { Connection, createConnection } from 'typeorm';
|
7
7
|
import * as dbutils from '../utils';
|
8
|
+
import JacksonIndexEntity from './entity/JacksonIndex';
|
9
|
+
import JacksonStoreEntity from './entity/JacksonStore';
|
10
|
+
import {
|
11
|
+
JacksonTTL,
|
12
|
+
JacksonTTL as JacksonTTLEntity,
|
13
|
+
} from './entity/JacksonTTL';
|
8
14
|
import { JacksonIndex } from './model/JacksonIndex';
|
9
15
|
import { JacksonStore } from './model/JacksonStore';
|
10
|
-
import { JacksonTTL } from './entity/JacksonTTL';
|
11
|
-
|
12
|
-
import JacksonStoreEntity from './entity/JacksonStore';
|
13
|
-
import JacksonIndexEntity from './entity/JacksonIndex';
|
14
|
-
import { JacksonTTL as JacksonTTLEntity } from './entity/JacksonTTL';
|
15
16
|
|
16
17
|
class Sql implements DatabaseDriver {
|
17
18
|
private options: DatabaseOption;
|
18
19
|
private connection!: Connection;
|
19
|
-
private storeRepository;
|
20
|
-
private indexRepository;
|
21
|
-
private ttlRepository;
|
20
|
+
private storeRepository;
|
21
|
+
private indexRepository;
|
22
|
+
private ttlRepository;
|
22
23
|
private ttlCleanup;
|
23
24
|
private timerId;
|
24
25
|
|
package/src/db/store.ts
CHANGED
@@ -6,7 +6,7 @@ class Store implements Storable {
|
|
6
6
|
private db: any;
|
7
7
|
private ttl: number;
|
8
8
|
|
9
|
-
constructor(namespace: string, db: any, ttl
|
9
|
+
constructor(namespace: string, db: any, ttl = 0) {
|
10
10
|
this.namespace = namespace;
|
11
11
|
this.db = db;
|
12
12
|
this.ttl = ttl;
|
@@ -43,7 +43,7 @@ class Store implements Storable {
|
|
43
43
|
}
|
44
44
|
|
45
45
|
export default {
|
46
|
-
new: (namespace: string, db: any, ttl
|
46
|
+
new: (namespace: string, db: any, ttl = 0): Storable => {
|
47
47
|
return new Store(namespace, db, ttl);
|
48
48
|
},
|
49
49
|
};
|
package/src/env.ts
CHANGED
@@ -9,7 +9,7 @@ const internalHostPort = +(process.env.INTERNAL_HOST_PORT || '6000');
|
|
9
9
|
|
10
10
|
const apiKeys = (process.env.JACKSON_API_KEYS || '').split(',');
|
11
11
|
|
12
|
-
const samlAudience = process.env.SAML_AUDIENCE;
|
12
|
+
const samlAudience = process.env.SAML_AUDIENCE || 'https://saml.boxyhq.com';
|
13
13
|
const preLoadedConfig = process.env.PRE_LOADED_CONFIG;
|
14
14
|
|
15
15
|
const idpEnabled = process.env.IDP_ENABLED;
|
package/src/index.ts
CHANGED
@@ -32,9 +32,12 @@ const defaultOpts = (opts: JacksonOption): JacksonOption => {
|
|
32
32
|
return newOpts;
|
33
33
|
};
|
34
34
|
|
35
|
-
|
35
|
+
const controllers = async (
|
36
36
|
opts: JacksonOption
|
37
|
-
): Promise<{
|
37
|
+
): Promise<{
|
38
|
+
apiController: SAMLConfig;
|
39
|
+
oauthController: OAuthController;
|
40
|
+
}> => {
|
38
41
|
opts = defaultOpts(opts);
|
39
42
|
|
40
43
|
const db = await DB.new(opts.db);
|
@@ -76,4 +79,6 @@ export default async function controllers(
|
|
76
79
|
apiController,
|
77
80
|
oauthController,
|
78
81
|
};
|
79
|
-
}
|
82
|
+
};
|
83
|
+
|
84
|
+
export default controllers;
|
package/src/jackson.ts
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
import cors from 'cors';
|
2
2
|
import express from 'express';
|
3
|
+
import { IOAuthController, ISAMLConfig } from 'saml-jackson';
|
3
4
|
import { JacksonError } from './controller/error';
|
4
5
|
import { extractAuthToken } from './controller/utils';
|
5
6
|
import env from './env';
|
6
|
-
import jackson from './index';
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
//import jackson from './index';
|
9
|
+
|
10
|
+
const jackson = require('./index');
|
11
|
+
|
12
|
+
let apiController: ISAMLConfig;
|
13
|
+
let oauthController: IOAuthController;
|
10
14
|
|
11
15
|
const oauthPath = '/oauth';
|
12
16
|
const apiPath = '/api/v1/saml';
|
@@ -62,7 +66,7 @@ app.get(oauthPath + '/userinfo', async (req, res) => {
|
|
62
66
|
}
|
63
67
|
|
64
68
|
if (!token) {
|
65
|
-
res.status(401).json({ message: 'Unauthorized' });
|
69
|
+
return res.status(401).json({ message: 'Unauthorized' });
|
66
70
|
}
|
67
71
|
|
68
72
|
const profile = await oauthController.userInfo(token);
|
@@ -80,8 +84,6 @@ const server = app.listen(env.hostPort, async () => {
|
|
80
84
|
`🚀 The path of the righteous server: http://${env.hostUrl}:${env.hostPort}`
|
81
85
|
);
|
82
86
|
|
83
|
-
// TODO: Fix it
|
84
|
-
// @ts-ignore
|
85
87
|
const ctrlrModule = await jackson(env);
|
86
88
|
|
87
89
|
apiController = ctrlrModule.apiController;
|
package/src/saml/saml.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
import saml from '@boxyhq/saml20';
|
2
|
+
import xml2js from 'xml2js';
|
3
|
+
import thumbprint from 'thumbprint';
|
4
|
+
import xmlcrypto from 'xml-crypto';
|
5
5
|
import * as rambda from 'rambda';
|
6
6
|
import xmlbuilder from 'xmlbuilder';
|
7
7
|
import crypto from 'crypto';
|
@@ -53,7 +53,8 @@ const request = ({
|
|
53
53
|
const id = idPrefix + crypto.randomBytes(10).toString('hex');
|
54
54
|
const date = new Date().toISOString();
|
55
55
|
|
56
|
-
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
57
|
+
const samlReq: Record<string, any> = {
|
57
58
|
'samlp:AuthnRequest': {
|
58
59
|
'@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
|
59
60
|
'@ID': id,
|
@@ -97,9 +98,7 @@ const request = ({
|
|
97
98
|
};
|
98
99
|
};
|
99
100
|
|
100
|
-
const parseAsync = async (
|
101
|
-
rawAssertion: string
|
102
|
-
): Promise<SAMLProfile | void> => {
|
101
|
+
const parseAsync = async (rawAssertion: string): Promise<SAMLProfile> => {
|
103
102
|
return new Promise((resolve, reject) => {
|
104
103
|
saml.parse(
|
105
104
|
rawAssertion,
|
@@ -118,7 +117,7 @@ const parseAsync = async (
|
|
118
117
|
const validateAsync = async (
|
119
118
|
rawAssertion: string,
|
120
119
|
options
|
121
|
-
): Promise<SAMLProfile
|
120
|
+
): Promise<SAMLProfile> => {
|
122
121
|
return new Promise((resolve, reject) => {
|
123
122
|
saml.validate(
|
124
123
|
rawAssertion,
|
package/src/test/api.test.ts
CHANGED
@@ -144,7 +144,7 @@ tap.test('controller/api', async (t) => {
|
|
144
144
|
t.equal(response.client_id, CLIENT_ID);
|
145
145
|
t.equal(response.provider, PROVIDER);
|
146
146
|
|
147
|
-
|
147
|
+
const savedConf = await apiController.getConfig({
|
148
148
|
clientID: CLIENT_ID,
|
149
149
|
});
|
150
150
|
|
package/src/test/oauth.test.ts
CHANGED
@@ -1,30 +1,36 @@
|
|
1
1
|
import crypto from 'crypto';
|
2
2
|
import { promises as fs } from 'fs';
|
3
3
|
import path from 'path';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
IOAuthController,
|
6
|
+
ISAMLConfig,
|
7
|
+
JacksonOption,
|
8
|
+
OAuthReqBody,
|
9
|
+
OAuthTokenReq,
|
10
|
+
SAMLResponsePayload,
|
11
|
+
} from 'saml-jackson';
|
5
12
|
import sinon from 'sinon';
|
6
13
|
import tap from 'tap';
|
7
14
|
import { JacksonError } from '../controller/error';
|
8
15
|
import readConfig from '../read-config';
|
9
16
|
import saml from '../saml/saml';
|
10
17
|
|
11
|
-
|
12
|
-
let
|
13
|
-
let oauthController;
|
18
|
+
let apiController: ISAMLConfig;
|
19
|
+
let oauthController: IOAuthController;
|
14
20
|
|
15
21
|
const code = '1234567890';
|
16
22
|
const token = '24c1550190dd6a5a9bd6fe2a8ff69d593121c7b9';
|
17
23
|
|
18
24
|
const metadataPath = path.join(__dirname, '/data/metadata');
|
19
25
|
|
20
|
-
const options = {
|
26
|
+
const options = <JacksonOption>{
|
21
27
|
externalUrl: 'https://my-cool-app.com',
|
22
28
|
samlAudience: 'https://saml.boxyhq.com',
|
23
29
|
samlPath: '/sso/oauth/saml',
|
24
30
|
db: {
|
25
31
|
engine: 'mem',
|
26
32
|
},
|
27
|
-
}
|
33
|
+
};
|
28
34
|
|
29
35
|
const samlConfig = {
|
30
36
|
tenant: 'boxyhq.com',
|
@@ -57,13 +63,13 @@ tap.teardown(async () => {
|
|
57
63
|
|
58
64
|
tap.test('authorize()', async (t) => {
|
59
65
|
t.test('Should throw an error if `redirect_uri` null', async (t) => {
|
60
|
-
const body = {
|
61
|
-
redirect_uri:
|
66
|
+
const body: Partial<OAuthReqBody> = {
|
67
|
+
redirect_uri: undefined,
|
62
68
|
state: 'state',
|
63
69
|
};
|
64
70
|
|
65
71
|
try {
|
66
|
-
await oauthController.authorize(body);
|
72
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
67
73
|
t.fail('Expecting JacksonError.');
|
68
74
|
} catch (err) {
|
69
75
|
const { message, statusCode } = err as JacksonError;
|
@@ -79,13 +85,13 @@ tap.test('authorize()', async (t) => {
|
|
79
85
|
});
|
80
86
|
|
81
87
|
t.test('Should throw an error if `state` null', async (t) => {
|
82
|
-
const body = {
|
88
|
+
const body: Partial<OAuthReqBody> = {
|
83
89
|
redirect_uri: 'https://example.com/',
|
84
|
-
state:
|
90
|
+
state: undefined,
|
85
91
|
};
|
86
92
|
|
87
93
|
try {
|
88
|
-
await oauthController.authorize(body);
|
94
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
89
95
|
|
90
96
|
t.fail('Expecting JacksonError.');
|
91
97
|
} catch (err) {
|
@@ -109,7 +115,7 @@ tap.test('authorize()', async (t) => {
|
|
109
115
|
};
|
110
116
|
|
111
117
|
try {
|
112
|
-
await oauthController.authorize(body);
|
118
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
113
119
|
|
114
120
|
t.fail('Expecting JacksonError.');
|
115
121
|
} catch (err) {
|
@@ -135,7 +141,7 @@ tap.test('authorize()', async (t) => {
|
|
135
141
|
};
|
136
142
|
|
137
143
|
try {
|
138
|
-
await oauthController.authorize(body);
|
144
|
+
await oauthController.authorize(<OAuthReqBody>body);
|
139
145
|
|
140
146
|
t.fail('Expecting JacksonError.');
|
141
147
|
} catch (err) {
|
@@ -159,7 +165,7 @@ tap.test('authorize()', async (t) => {
|
|
159
165
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
160
166
|
};
|
161
167
|
|
162
|
-
const response = await oauthController.authorize(body);
|
168
|
+
const response = await oauthController.authorize(<OAuthReqBody>body);
|
163
169
|
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
164
170
|
|
165
171
|
t.ok('redirect_url' in response, 'got the Idp authorize URL');
|
@@ -179,7 +185,9 @@ tap.test('samlResponse()', async (t) => {
|
|
179
185
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
180
186
|
};
|
181
187
|
|
182
|
-
const { redirect_url } = await oauthController.authorize(
|
188
|
+
const { redirect_url } = await oauthController.authorize(
|
189
|
+
<OAuthReqBody>authBody
|
190
|
+
);
|
183
191
|
|
184
192
|
const relayState = new URLSearchParams(new URL(redirect_url).search).get(
|
185
193
|
'RelayState'
|
@@ -191,12 +199,12 @@ tap.test('samlResponse()', async (t) => {
|
|
191
199
|
);
|
192
200
|
|
193
201
|
t.test('Should throw an error if `RelayState` is missing', async (t) => {
|
194
|
-
const responseBody = {
|
202
|
+
const responseBody: Partial<SAMLResponsePayload> = {
|
195
203
|
SAMLResponse: rawResponse,
|
196
204
|
};
|
197
205
|
|
198
206
|
try {
|
199
|
-
await oauthController.samlResponse(responseBody);
|
207
|
+
await oauthController.samlResponse(<SAMLResponsePayload>responseBody);
|
200
208
|
|
201
209
|
t.fail('Expecting JacksonError.');
|
202
210
|
} catch (err) {
|
@@ -224,10 +232,13 @@ tap.test('samlResponse()', async (t) => {
|
|
224
232
|
const stubValidateAsync = sinon
|
225
233
|
.stub(saml, 'validateAsync')
|
226
234
|
.resolves({ audience: '', claims: {}, issuer: '', sessionIndex: '' });
|
235
|
+
|
227
236
|
//@ts-ignore
|
228
237
|
const stubRandomBytes = sinon.stub(crypto, 'randomBytes').returns(code);
|
229
238
|
|
230
|
-
const response = await oauthController.samlResponse(
|
239
|
+
const response = await oauthController.samlResponse(
|
240
|
+
<SAMLResponsePayload>responseBody
|
241
|
+
);
|
231
242
|
|
232
243
|
const params = new URLSearchParams(new URL(response.redirect_url).search);
|
233
244
|
|
@@ -257,7 +268,7 @@ tap.test('token()', (t) => {
|
|
257
268
|
};
|
258
269
|
|
259
270
|
try {
|
260
|
-
await oauthController.token(body);
|
271
|
+
await oauthController.token(<OAuthTokenReq>body);
|
261
272
|
|
262
273
|
t.fail('Expecting JacksonError.');
|
263
274
|
} catch (err) {
|
@@ -280,7 +291,7 @@ tap.test('token()', (t) => {
|
|
280
291
|
};
|
281
292
|
|
282
293
|
try {
|
283
|
-
await oauthController.token(body);
|
294
|
+
await oauthController.token(<OAuthTokenReq>body);
|
284
295
|
|
285
296
|
t.fail('Expecting JacksonError.');
|
286
297
|
} catch (err) {
|
@@ -293,16 +304,15 @@ tap.test('token()', (t) => {
|
|
293
304
|
});
|
294
305
|
|
295
306
|
t.test('Should throw an error if `code` is invalid', async (t) => {
|
296
|
-
const body = {
|
307
|
+
const body: Partial<OAuthTokenReq> = {
|
297
308
|
grant_type: 'authorization_code',
|
298
309
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
299
310
|
client_secret: 'some-secret',
|
300
|
-
redirect_uri: null,
|
301
311
|
code: 'invalid-code',
|
302
312
|
};
|
303
313
|
|
304
314
|
try {
|
305
|
-
await oauthController.token(body);
|
315
|
+
await oauthController.token(<OAuthTokenReq>body);
|
306
316
|
|
307
317
|
t.fail('Expecting JacksonError.');
|
308
318
|
} catch (err) {
|
@@ -315,11 +325,10 @@ tap.test('token()', (t) => {
|
|
315
325
|
});
|
316
326
|
|
317
327
|
t.test('Should return the `access_token` for a valid request', async (t) => {
|
318
|
-
const body = {
|
328
|
+
const body: Partial<OAuthTokenReq> = {
|
319
329
|
grant_type: 'authorization_code',
|
320
330
|
client_id: `tenant=${samlConfig.tenant}&product=${samlConfig.product}`,
|
321
331
|
client_secret: 'some-secret',
|
322
|
-
redirect_uri: null,
|
323
332
|
code: code,
|
324
333
|
};
|
325
334
|
|
@@ -329,7 +338,7 @@ tap.test('token()', (t) => {
|
|
329
338
|
//@ts-ignore
|
330
339
|
.returns(token);
|
331
340
|
|
332
|
-
const response = await oauthController.token(body);
|
341
|
+
const response = await oauthController.token(<OAuthTokenReq>body);
|
333
342
|
|
334
343
|
t.ok(stubRandomBytes.calledOnce, 'randomBytes called once');
|
335
344
|
t.ok('access_token' in response, 'includes access_token');
|
package/src/typings.ts
CHANGED
@@ -51,7 +51,7 @@ declare module 'saml-jackson' {
|
|
51
51
|
authorize(body: OAuthReqBody): Promise<{ redirect_url: string }>;
|
52
52
|
samlResponse(body: SAMLResponsePayload): Promise<{ redirect_url: string }>;
|
53
53
|
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
54
|
-
userInfo(
|
54
|
+
userInfo(token: string): Promise<Profile>;
|
55
55
|
}
|
56
56
|
|
57
57
|
export interface OAuthReqBody {
|
@@ -104,7 +104,7 @@ declare module 'saml-jackson' {
|
|
104
104
|
key: string,
|
105
105
|
val: any,
|
106
106
|
ttl: number,
|
107
|
-
indexes: Index[]
|
107
|
+
...indexes: Index[]
|
108
108
|
): Promise<any>;
|
109
109
|
delete(namespace: string, key: string): Promise<any>;
|
110
110
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
@@ -159,7 +159,7 @@ declare module 'saml-jackson' {
|
|
159
159
|
export interface JacksonOption {
|
160
160
|
externalUrl: string;
|
161
161
|
samlPath: string;
|
162
|
-
samlAudience
|
162
|
+
samlAudience: string;
|
163
163
|
preLoadedConfig?: string;
|
164
164
|
idpEnabled?: boolean;
|
165
165
|
db: DatabaseOption;
|