Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/auth-check-required-sso.test.ts
5837 views
1
import {
2
emailBelongsToDomain,
3
getEmailDomain,
4
checkRequiredSSO,
5
} from "./auth-check-required-sso";
6
import { Strategy } from "./types/sso";
7
8
const SSO: Readonly<Omit<Strategy, "name" | "exclusiveDomains">> = {
9
display: "",
10
backgroundColor: "",
11
public: false,
12
doNotHide: true,
13
updateOnLogin: true,
14
} as const;
15
16
describe("Check Required SSO", () => {
17
test("getEmailDomain", () => {
18
expect(getEmailDomain("[email protected]")).toBe("bar.com");
19
expect(getEmailDomain("[email protected]")).toBe("bar.co.uk");
20
});
21
22
test("emailBelongsToDomain", () => {
23
expect(emailBelongsToDomain("foo.com", "foo.com")).toBe(true);
24
expect(emailBelongsToDomain("bar.foo.com", "foo.com")).toBe(true);
25
expect(emailBelongsToDomain("foo.com", "bar.com")).toBe(false);
26
expect(emailBelongsToDomain("foo.com", "foo.co.uk")).toBe(false);
27
expect(emailBelongsToDomain("foo.com", "foo.com.uk")).toBe(false);
28
expect(emailBelongsToDomain("foobar.com", "bar.com")).toBe(false);
29
expect(emailBelongsToDomain("foobar.com", "bazfoobar.com")).toBe(false);
30
expect(emailBelongsToDomain("foobar.com", "*")).toBe(false);
31
});
32
33
const foo = { name: "foo", exclusiveDomains: ["foo.co.uk"], ...SSO };
34
const bar = { name: "bar", exclusiveDomains: ["*"], ...SSO };
35
const baz = {
36
name: "baz",
37
exclusiveDomains: ["baz.com", "abc.com"],
38
...SSO,
39
};
40
41
test("checkRequiredSSO", () => {
42
const strategies: Strategy[] = [foo, baz] as const;
43
44
expect(checkRequiredSSO({ email: "[email protected]", strategies })?.name).toEqual(
45
"baz",
46
);
47
expect(
48
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
49
).toEqual("baz");
50
expect(
51
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
52
).toEqual("foo");
53
expect(
54
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
55
).toEqual("foo");
56
// no match on naive substring from the right
57
expect(
58
checkRequiredSSO({ email: "[email protected]", strategies }),
59
).toBeUndefined();
60
// no catch-all for an unrelated domain, returns no strategy
61
expect(
62
checkRequiredSSO({ email: "[email protected]", strategies }),
63
).toBeUndefined();
64
});
65
66
test("checkRequiredSSO/catchall", () => {
67
const strategies: Strategy[] = [foo, bar, baz] as const;
68
69
expect(checkRequiredSSO({ email: "[email protected]", strategies })?.name).toEqual(
70
"baz",
71
);
72
expect(
73
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
74
).toEqual("baz");
75
expect(
76
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
77
).toEqual("foo");
78
// this is the essential difference to above
79
expect(
80
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
81
).toEqual("bar");
82
});
83
84
test("checkRequiredSSO/specificStrategy", () => {
85
const strategies: Strategy[] = [foo, bar, baz] as const;
86
87
// When specificStrategy is set, only that strategy should match
88
expect(
89
checkRequiredSSO({
90
email: "[email protected]",
91
strategies,
92
specificStrategy: "baz",
93
})?.name,
94
).toEqual("baz");
95
96
// Should not match other strategies even if domain matches
97
expect(
98
checkRequiredSSO({
99
email: "[email protected]",
100
strategies,
101
specificStrategy: "foo",
102
}),
103
).toBeUndefined();
104
105
// Wildcard should work with specificStrategy
106
expect(
107
checkRequiredSSO({
108
email: "[email protected]",
109
strategies,
110
specificStrategy: "bar",
111
})?.name,
112
).toEqual("bar");
113
114
// SECURITY: specificStrategy should prevent wildcard from other strategies
115
expect(
116
checkRequiredSSO({
117
email: "[email protected]",
118
strategies,
119
specificStrategy: "foo",
120
}),
121
).toBeUndefined();
122
});
123
124
test("getEmailDomain/edge-cases", () => {
125
// Normal cases with whitespace and case variations
126
expect(getEmailDomain(" [email protected] ")).toBe("bar.com");
127
expect(getEmailDomain("[email protected]")).toBe("bar.com");
128
expect(getEmailDomain("[email protected]")).toBe("bar.com");
129
130
// Note: Multiple @ signs (like "foo@[email protected]") are rejected by
131
// is_valid_email_address before getEmailDomain is called, so no test needed
132
});
133
134
test("emailBelongsToDomain/normalized-domains", () => {
135
// Both emailDomain and ssoDomain are normalized to lowercase at the source
136
expect(emailBelongsToDomain("bar.com", "bar.com")).toBe(true);
137
expect(emailBelongsToDomain("foo.bar.com", "bar.com")).toBe(true);
138
139
// All domains from getEmailDomain and database queries are lowercase
140
expect(emailBelongsToDomain("university.edu", "university.edu")).toBe(true);
141
expect(emailBelongsToDomain("mail.university.edu", "university.edu")).toBe(
142
true,
143
);
144
expect(emailBelongsToDomain("foo.com", "foo.com")).toBe(true);
145
146
// Edge case: ensure no partial string matches
147
expect(emailBelongsToDomain("barbarbar.com", "bar.com")).toBe(false);
148
expect(emailBelongsToDomain("xbar.com", "bar.com")).toBe(false);
149
});
150
151
test("checkRequiredSSO/invalid-inputs", () => {
152
const strategies: Strategy[] = [foo, bar, baz] as const;
153
154
// Invalid email addresses should return undefined
155
expect(checkRequiredSSO({ email: "", strategies })).toBeUndefined();
156
expect(checkRequiredSSO({ email: undefined, strategies })).toBeUndefined();
157
expect(
158
checkRequiredSSO({ email: "notanemail", strategies }),
159
).toBeUndefined();
160
expect(
161
checkRequiredSSO({ email: "@domain.com", strategies }),
162
).toBeUndefined();
163
expect(checkRequiredSSO({ email: "user@", strategies })).toBeUndefined();
164
165
// No strategies
166
expect(
167
checkRequiredSSO({ email: "[email protected]", strategies: [] }),
168
).toBeUndefined();
169
expect(
170
checkRequiredSSO({ email: "[email protected]", strategies: undefined }),
171
).toBeUndefined();
172
});
173
174
test("checkRequiredSSO/strategy-priority", () => {
175
// When multiple strategies could match, first one wins
176
const dup1 = { name: "dup1", exclusiveDomains: ["test.com"], ...SSO };
177
const dup2 = { name: "dup2", exclusiveDomains: ["test.com"], ...SSO };
178
const strategies: Strategy[] = [dup1, dup2] as const;
179
180
expect(checkRequiredSSO({ email: "[email protected]", strategies })?.name).toEqual(
181
"dup1",
182
);
183
184
// Wildcard order matters too
185
const wild1 = { name: "wild1", exclusiveDomains: ["*"], ...SSO };
186
const wild2 = { name: "wild2", exclusiveDomains: ["*"], ...SSO };
187
const wildStrategies: Strategy[] = [wild1, wild2] as const;
188
189
expect(
190
checkRequiredSSO({ email: "[email protected]", strategies: wildStrategies })
191
?.name,
192
).toEqual("wild1");
193
});
194
195
test("checkRequiredSSO/empty-exclusiveDomains", () => {
196
const emptyDomains = {
197
name: "empty",
198
exclusiveDomains: [],
199
...SSO,
200
};
201
const strategies: Strategy[] = [emptyDomains] as const;
202
203
expect(
204
checkRequiredSSO({ email: "[email protected]", strategies }),
205
).toBeUndefined();
206
});
207
208
test("checkRequiredSSO/subdomain-matching", () => {
209
const strategies: Strategy[] = [baz] as const;
210
211
// Direct domain match
212
expect(checkRequiredSSO({ email: "[email protected]", strategies })?.name).toEqual(
213
"baz",
214
);
215
216
// Subdomain matches
217
expect(
218
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
219
).toEqual("baz");
220
expect(
221
checkRequiredSSO({ email: "[email protected]", strategies })?.name,
222
).toEqual("baz");
223
224
// Should not match partial string (already tested but important)
225
expect(
226
checkRequiredSSO({ email: "[email protected]", strategies }),
227
).toBeUndefined();
228
expect(
229
checkRequiredSSO({ email: "[email protected]", strategies }),
230
).toBeUndefined();
231
});
232
});
233
234