oidc-spa 8.5.5 → 8.6.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/core/createOidc.js +16 -1
- package/core/createOidc.js.map +1 -1
- package/core/tokenExfiltrationDefense.js +17 -44
- package/core/tokenExfiltrationDefense.js.map +1 -1
- package/core/tokenPlaceholderSubstitution.d.ts +2 -5
- package/core/tokenPlaceholderSubstitution.js +73 -38
- package/core/tokenPlaceholderSubstitution.js.map +1 -1
- package/esm/core/createOidc.js +16 -1
- package/esm/core/createOidc.js.map +1 -1
- package/esm/core/tokenExfiltrationDefense.js +18 -45
- package/esm/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/core/tokenPlaceholderSubstitution.d.ts +2 -5
- package/esm/core/tokenPlaceholderSubstitution.js +72 -37
- package/esm/core/tokenPlaceholderSubstitution.js.map +1 -1
- package/package.json +1 -1
- package/src/core/createOidc.ts +18 -0
- package/src/core/tokenExfiltrationDefense.ts +18 -45
- package/src/core/tokenPlaceholderSubstitution.ts +98 -45
|
@@ -2,7 +2,7 @@ import { assert } from "../tools/tsafe/assert";
|
|
|
2
2
|
|
|
3
3
|
let isTokenSubstitutionEnabled = false;
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function markTokenSubstitutionAsEnabled() {
|
|
6
6
|
isTokenSubstitutionEnabled = true;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -18,10 +18,61 @@ type Tokens = {
|
|
|
18
18
|
|
|
19
19
|
const entries: {
|
|
20
20
|
configId: string;
|
|
21
|
-
tokens: Tokens;
|
|
22
21
|
id: number;
|
|
22
|
+
tokens: Tokens;
|
|
23
|
+
tokens_placeholder: Tokens;
|
|
23
24
|
}[] = [];
|
|
24
25
|
|
|
26
|
+
function generatePlaceholderForToken(params: {
|
|
27
|
+
tokenType: "id_token" | "access_token" | "refresh_token";
|
|
28
|
+
token_real: string;
|
|
29
|
+
id: number;
|
|
30
|
+
}): string {
|
|
31
|
+
const { tokenType, token_real, id } = params;
|
|
32
|
+
|
|
33
|
+
const match = token_real.match(/^([A-Za-z0-9\-_]+)\.([A-Za-z0-9\-_]+)\.([A-Za-z0-9\-_]+)$/);
|
|
34
|
+
|
|
35
|
+
if (match === null) {
|
|
36
|
+
assert(tokenType !== "id_token", "39232932927");
|
|
37
|
+
return `${tokenType}_placeholder_${id}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const [, header_b64, payload_b64, signature_b64] = match;
|
|
41
|
+
|
|
42
|
+
const signatureByteLength = (() => {
|
|
43
|
+
const b64 = signature_b64
|
|
44
|
+
.replace(/-/g, "+")
|
|
45
|
+
.replace(/_/g, "/")
|
|
46
|
+
.padEnd(Math.ceil(signature_b64.length / 4) * 4, "=");
|
|
47
|
+
|
|
48
|
+
const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
49
|
+
return (b64.length * 3) / 4 - padding;
|
|
50
|
+
})();
|
|
51
|
+
|
|
52
|
+
const targetSigB64Length = Math.ceil((signatureByteLength * 4) / 3);
|
|
53
|
+
|
|
54
|
+
const sig_placeholder = (function makeZeroPaddedBase64UrlString(
|
|
55
|
+
targetLength: number,
|
|
56
|
+
seed: string
|
|
57
|
+
): string {
|
|
58
|
+
const PAD = "A";
|
|
59
|
+
|
|
60
|
+
let out = seed.slice(0, targetLength);
|
|
61
|
+
|
|
62
|
+
if (out.length < targetLength) {
|
|
63
|
+
out = out + PAD.repeat(targetLength - out.length);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (out.length % 4 === 1) {
|
|
67
|
+
out = out.slice(0, -1) + PAD;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return out;
|
|
71
|
+
})(targetSigB64Length, `sig_placeholder_${id}_`);
|
|
72
|
+
|
|
73
|
+
return `${header_b64}.${payload_b64}.${sig_placeholder}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
25
76
|
let counter = Math.floor(Math.random() * 1_000_000) + 1_000_000;
|
|
26
77
|
|
|
27
78
|
export function getTokensPlaceholders(params: { configId: string; tokens: Tokens }): Tokens {
|
|
@@ -48,66 +99,68 @@ export function getTokensPlaceholders(params: { configId: string; tokens: Tokens
|
|
|
48
99
|
const id = counter++;
|
|
49
100
|
|
|
50
101
|
const entry_new: (typeof entries)[number] = {
|
|
51
|
-
id,
|
|
52
102
|
configId,
|
|
103
|
+
id,
|
|
53
104
|
tokens: {
|
|
54
|
-
accessToken: tokens.accessToken,
|
|
55
105
|
idToken: tokens.idToken,
|
|
106
|
+
accessToken: tokens.accessToken,
|
|
56
107
|
refreshToken: tokens.refreshToken
|
|
108
|
+
},
|
|
109
|
+
tokens_placeholder: {
|
|
110
|
+
idToken: generatePlaceholderForToken({
|
|
111
|
+
tokenType: "id_token",
|
|
112
|
+
id,
|
|
113
|
+
token_real: tokens.idToken
|
|
114
|
+
}),
|
|
115
|
+
accessToken: generatePlaceholderForToken({
|
|
116
|
+
tokenType: "access_token",
|
|
117
|
+
id,
|
|
118
|
+
token_real: tokens.accessToken
|
|
119
|
+
}),
|
|
120
|
+
refreshToken:
|
|
121
|
+
tokens.refreshToken === undefined
|
|
122
|
+
? undefined
|
|
123
|
+
: generatePlaceholderForToken({
|
|
124
|
+
tokenType: "refresh_token",
|
|
125
|
+
id,
|
|
126
|
+
token_real: tokens.refreshToken
|
|
127
|
+
})
|
|
57
128
|
}
|
|
58
129
|
};
|
|
59
130
|
|
|
60
131
|
entries.push(entry_new);
|
|
61
132
|
|
|
62
|
-
return
|
|
63
|
-
accessToken: `access_token_placeholder_${id}`,
|
|
64
|
-
idToken: `id_token_placeholder_${id}`,
|
|
65
|
-
refreshToken: tokens.refreshToken === undefined ? undefined : `refresh_token_placeholder_${id}`
|
|
66
|
-
};
|
|
133
|
+
return entry_new.tokens_placeholder;
|
|
67
134
|
}
|
|
68
135
|
|
|
69
|
-
export function substitutePlaceholderByRealToken(
|
|
70
|
-
text
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
const { text, doEncodeUriComponent } = params;
|
|
136
|
+
export function substitutePlaceholderByRealToken(text: string): string {
|
|
137
|
+
if (!text.includes("_placeholder_")) {
|
|
138
|
+
return text;
|
|
139
|
+
}
|
|
74
140
|
|
|
75
141
|
let text_modified = text;
|
|
76
142
|
|
|
77
|
-
for (const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const entry = entries.find(e => e.id === id);
|
|
86
|
-
|
|
87
|
-
if (!entry) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
[
|
|
90
|
-
"oidc-spa: Outdated token used to make a request.",
|
|
91
|
-
"Token should not be stored at the application level, when a token",
|
|
92
|
-
"is needed, it should be requested and used immediately."
|
|
93
|
-
].join(" ")
|
|
94
|
-
);
|
|
95
|
-
}
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (!text.includes(`${entry.id}`)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const tokenType of ["idToken", "accessToken", "refreshToken"] as const) {
|
|
149
|
+
const placeholder = entry.tokens_placeholder[tokenType];
|
|
96
150
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return entry.tokens.accessToken;
|
|
101
|
-
case "id_token":
|
|
102
|
-
return entry.tokens.idToken;
|
|
103
|
-
case "refresh_token":
|
|
104
|
-
assert(entry.tokens.refreshToken !== undefined, "204392284");
|
|
105
|
-
return entry.tokens.refreshToken;
|
|
151
|
+
if (tokenType === "refreshToken") {
|
|
152
|
+
if (placeholder === undefined) {
|
|
153
|
+
continue;
|
|
106
154
|
}
|
|
107
|
-
}
|
|
155
|
+
}
|
|
156
|
+
assert(placeholder !== undefined, "023948092393");
|
|
157
|
+
|
|
158
|
+
const realToken = entry.tokens[tokenType];
|
|
108
159
|
|
|
109
|
-
|
|
110
|
-
|
|
160
|
+
assert(realToken !== undefined, "02394809239328");
|
|
161
|
+
|
|
162
|
+
text_modified = text_modified.split(placeholder).join(realToken);
|
|
163
|
+
}
|
|
111
164
|
}
|
|
112
165
|
|
|
113
166
|
return text_modified;
|