@adminforth/open-signup 2.0.2 → 2.0.3
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/.woodpecker/buildRelease.sh +2 -3
- package/build.log +40 -1
- package/dist/index.js +6 -4
- package/index.ts +7 -6
- package/package.json +3 -2
- package/tests/signupEmailCase.test.mjs +113 -0
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
#!/bin/bash
|
|
3
3
|
|
|
4
4
|
# write npm run output both to console and to build.log
|
|
5
|
-
npm
|
|
5
|
+
npm test 2>&1 | tee build.log
|
|
6
6
|
build_status=${PIPESTATUS[0]}
|
|
7
7
|
|
|
8
|
-
# if
|
|
9
|
-
# then exit with the status code from the npm run build
|
|
8
|
+
# if exit status is not 0, exit with the status code from the build+test run
|
|
10
9
|
if [ $build_status -ne 0 ]; then
|
|
11
10
|
echo "Build failed. Exiting with status code $build_status"
|
|
12
11
|
exit $build_status
|
package/build.log
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
|
|
2
|
+
> @adminforth/open-signup@1.0.9 test
|
|
3
|
+
> npm run build && node --test tests/*.test.mjs
|
|
4
|
+
|
|
5
|
+
|
|
2
6
|
> @adminforth/open-signup@1.0.9 build
|
|
3
7
|
> tsc && rsync -av --exclude 'node_modules' custom dist/
|
|
4
8
|
|
|
@@ -8,5 +12,40 @@ custom/SignupPage.vue
|
|
|
8
12
|
custom/SignupUnderLogin.vue
|
|
9
13
|
custom/tsconfig.json
|
|
10
14
|
|
|
11
|
-
sent 14,
|
|
15
|
+
sent 14,167 bytes received 77 bytes 28,488.00 bytes/sec
|
|
12
16
|
total size is 13,884 speedup is 0.97
|
|
17
|
+
TAP version 13
|
|
18
|
+
# Subtest: a case-variant signup is rejected instead of creating a second account
|
|
19
|
+
ok 1 - a case-variant signup is rejected instead of creating a second account
|
|
20
|
+
---
|
|
21
|
+
duration_ms: 45.208471
|
|
22
|
+
...
|
|
23
|
+
# Subtest: case folding does not depend on which column is the auth username field
|
|
24
|
+
ok 2 - case folding does not depend on which column is the auth username field
|
|
25
|
+
---
|
|
26
|
+
duration_ms: 43.686471
|
|
27
|
+
...
|
|
28
|
+
# Subtest: a configured column normalize runs on the folded value, and the stored value is the one probed
|
|
29
|
+
ok 3 - a configured column normalize runs on the folded value, and the stored value is the one probed
|
|
30
|
+
---
|
|
31
|
+
duration_ms: 48.158384
|
|
32
|
+
...
|
|
33
|
+
# Subtest: the probed value survives the column normalize core re-applies on write
|
|
34
|
+
ok 4 - the probed value survives the column normalize core re-applies on write
|
|
35
|
+
---
|
|
36
|
+
duration_ms: 41.121963
|
|
37
|
+
...
|
|
38
|
+
# Subtest: a login whose account lookup finds nothing returns an error instead of crashing
|
|
39
|
+
ok 5 - a login whose account lookup finds nothing returns an error instead of crashing
|
|
40
|
+
---
|
|
41
|
+
duration_ms: 0.192068
|
|
42
|
+
...
|
|
43
|
+
1..5
|
|
44
|
+
# tests 5
|
|
45
|
+
# suites 0
|
|
46
|
+
# pass 5
|
|
47
|
+
# fail 0
|
|
48
|
+
# cancelled 0
|
|
49
|
+
# skipped 0
|
|
50
|
+
# todo 0
|
|
51
|
+
# duration_ms 345.451587
|
package/dist/index.js
CHANGED
|
@@ -115,12 +115,11 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
115
115
|
const originList = Array.isArray(rawOrigins) ? rawOrigins : [rawOrigins];
|
|
116
116
|
return originList.map(o => new URL(o).origin);
|
|
117
117
|
}
|
|
118
|
+
// Unconditional fold: this value is both the lookup filter and the value stored.
|
|
118
119
|
normalizeUsername(email) {
|
|
119
|
-
|
|
120
|
-
return email;
|
|
121
|
-
}
|
|
120
|
+
const folded = typeof email === 'string' ? email.toLowerCase() : email;
|
|
122
121
|
const normalize = this.emailField.normalize;
|
|
123
|
-
return normalize ? normalize(
|
|
122
|
+
return normalize ? normalize(folded) : folded;
|
|
124
123
|
}
|
|
125
124
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
126
125
|
if (this.options.confirmEmails) {
|
|
@@ -142,6 +141,9 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
142
141
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
142
|
const username = this.normalizeUsername(email);
|
|
144
143
|
const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, username));
|
|
144
|
+
if (!userRecord) {
|
|
145
|
+
return { allowedLogin: false, error: 'User not found' };
|
|
146
|
+
}
|
|
145
147
|
const adminUser = {
|
|
146
148
|
dbUser: userRecord,
|
|
147
149
|
pk: userRecord[this.authResource.columns.find((col) => col.primaryKey).name],
|
package/index.ts
CHANGED
|
@@ -130,13 +130,11 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
130
130
|
return originList.map(o => new URL(o).origin);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Unconditional fold: this value is both the lookup filter and the value stored.
|
|
133
134
|
normalizeUsername(email: string): string {
|
|
134
|
-
|
|
135
|
-
return email;
|
|
136
|
-
}
|
|
137
|
-
|
|
135
|
+
const folded = typeof email === 'string' ? email.toLowerCase() : email;
|
|
138
136
|
const normalize = (this.emailField as AdminForthResourceColumn & { normalize?: (value: any) => any }).normalize;
|
|
139
|
-
return normalize ? normalize(
|
|
137
|
+
return normalize ? normalize(folded) : folded;
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
@@ -162,7 +160,10 @@ export default class OpenSignupPlugin extends AdminForthPlugin {
|
|
|
162
160
|
async doLogin(email: string, response: any, extra: HttpExtra): Promise<{ error?: string; allowedLogin: boolean; redirectTo?: string; }> {
|
|
163
161
|
const username = this.normalizeUsername(email);
|
|
164
162
|
const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.emailField.name, username));
|
|
165
|
-
|
|
163
|
+
if (!userRecord) {
|
|
164
|
+
return { allowedLogin: false, error: 'User not found' };
|
|
165
|
+
}
|
|
166
|
+
const adminUser = {
|
|
166
167
|
dbUser: userRecord,
|
|
167
168
|
pk: userRecord[this.authResource.columns.find((col) => col.primaryKey).name],
|
|
168
169
|
username,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/open-signup",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://adminforth.dev/docs/tutorial/Plugins/open-signup/",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
12
|
+
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/",
|
|
13
|
+
"test": "npm run build && node --test tests/*.test.mjs"
|
|
13
14
|
},
|
|
14
15
|
"repository": {
|
|
15
16
|
"type": "git",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import OpenSignupPlugin from '../dist/index.js';
|
|
4
|
+
|
|
5
|
+
function makePlugin({ usernameField = 'email', normalize } = {}) {
|
|
6
|
+
const records = [];
|
|
7
|
+
const cookies = [];
|
|
8
|
+
const plugin = Object.create(OpenSignupPlugin.prototype);
|
|
9
|
+
|
|
10
|
+
plugin.options = {
|
|
11
|
+
emailField: 'email',
|
|
12
|
+
passwordHashField: 'password_hash',
|
|
13
|
+
expectedOrigin: 'https://app.example.com',
|
|
14
|
+
};
|
|
15
|
+
plugin.pluginInstanceId = 'open-signup-test';
|
|
16
|
+
plugin.emailField = { name: 'email', ...(normalize ? { normalize } : {}) };
|
|
17
|
+
plugin.passwordField = { minLength: 8, maxLength: 64 };
|
|
18
|
+
plugin.authResource = {
|
|
19
|
+
resourceId: 'users',
|
|
20
|
+
columns: [{ name: 'id', primaryKey: true }, { name: 'email' }],
|
|
21
|
+
};
|
|
22
|
+
plugin.adminforth = {
|
|
23
|
+
config: { auth: { usernameField }, customization: { brandName: 'Test' } },
|
|
24
|
+
resource: () => ({
|
|
25
|
+
get: async (filter) => records.find((record) => record.email === filter.value) ?? null,
|
|
26
|
+
create: async (record) => {
|
|
27
|
+
// core applies the column's normalize again on write, so the harness has to as well
|
|
28
|
+
const email = normalize ? normalize(record.email) : record.email;
|
|
29
|
+
const created = { id: `user-${records.length + 1}`, ...record, email };
|
|
30
|
+
records.push(created);
|
|
31
|
+
return created;
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
restApi: { processLoginCallbacks: async () => {} },
|
|
35
|
+
auth: { setAuthCookie: (args) => cookies.push(args) },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let handler;
|
|
39
|
+
plugin.setupEndpoints({
|
|
40
|
+
endpoint: (options) => {
|
|
41
|
+
if (options.path.endsWith('/signup')) {
|
|
42
|
+
handler = options.handler;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const signup = (email) => handler({
|
|
48
|
+
body: { email, url: 'https://app.example.com/signup', password: 'S3cret-pass' },
|
|
49
|
+
response: {},
|
|
50
|
+
headers: {},
|
|
51
|
+
query: {},
|
|
52
|
+
cookies: [],
|
|
53
|
+
requestUrl: '/plugin/open-signup-test/signup',
|
|
54
|
+
tr: async (message) => message,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { plugin, records, cookies, signup };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
test('a case-variant signup is rejected instead of creating a second account', async () => {
|
|
61
|
+
const { records, signup, cookies } = makePlugin();
|
|
62
|
+
|
|
63
|
+
// the first address is deliberately mixed-case: it is what pins the stored value to the folded one
|
|
64
|
+
const first = await signup('Bob@Example.COM');
|
|
65
|
+
assert.equal(first.allowedLogin, true);
|
|
66
|
+
assert.equal(records.length, 1);
|
|
67
|
+
assert.equal(records[0].email, 'bob@example.com');
|
|
68
|
+
assert.equal(cookies.at(-1).username, 'bob@example.com');
|
|
69
|
+
|
|
70
|
+
const second = await signup('bob@example.com');
|
|
71
|
+
assert.match(second.error, /already exists/i);
|
|
72
|
+
assert.equal(records.length, 1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('case folding does not depend on which column is the auth username field', async () => {
|
|
76
|
+
const { records, signup } = makePlugin({ usernameField: 'login' });
|
|
77
|
+
|
|
78
|
+
await signup('bob@example.com');
|
|
79
|
+
const second = await signup('BOB@example.com');
|
|
80
|
+
|
|
81
|
+
assert.match(second.error, /already exists/i);
|
|
82
|
+
assert.equal(records.length, 1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('a configured column normalize runs on the folded value, and the stored value is the one probed', async () => {
|
|
86
|
+
const { records, signup } = makePlugin({ normalize: (value) => value.trim() });
|
|
87
|
+
|
|
88
|
+
await signup(' bob@example.com ');
|
|
89
|
+
const second = await signup('Bob@Example.com');
|
|
90
|
+
|
|
91
|
+
assert.match(second.error, /already exists/i);
|
|
92
|
+
assert.equal(records.length, 1);
|
|
93
|
+
assert.equal(records[0].email, 'bob@example.com');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('the probed value survives the column normalize core re-applies on write', async () => {
|
|
97
|
+
const { records, signup } = makePlugin({ normalize: (value) => value.toUpperCase() });
|
|
98
|
+
|
|
99
|
+
await signup('bob@example.com');
|
|
100
|
+
const second = await signup('Bob@Example.com');
|
|
101
|
+
|
|
102
|
+
assert.match(second.error, /already exists/i);
|
|
103
|
+
assert.equal(records.length, 1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('a login whose account lookup finds nothing returns an error instead of crashing', async () => {
|
|
107
|
+
const { plugin } = makePlugin();
|
|
108
|
+
|
|
109
|
+
const result = await plugin.doLogin('nobody@example.com', {}, {});
|
|
110
|
+
|
|
111
|
+
assert.equal(result.allowedLogin, false);
|
|
112
|
+
assert.match(result.error, /not found/i);
|
|
113
|
+
});
|