Skip to content

Commit bcf6699

Browse files
committed
Support mapped schema field
Closes #31
1 parent 253450a commit bcf6699

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

.changeset/some-corners-relate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'better-auth-harmony': minor
3+
---
4+
5+
Support mapped schema field (#31)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { afterAll, describe, expect, it, vi } from 'vitest';
2+
// eslint-disable-next-line import/no-relative-packages -- couldn't find a better way to include it
3+
import { getTestInstance } from '../../../../better-auth/packages/better-auth/src/test-utils/test-instance';
4+
import emailHarmony, { type UserWithNormalizedEmail } from '.';
5+
import { allEmail, allEmailSignIn, emailForget, emailSignUp } from './matchers';
6+
// eslint-disable-next-line import/no-relative-packages -- couldn't find a better way to include it
7+
import type { BetterAuthPlugin } from '../../../../better-auth/packages/better-auth/src/types';
8+
9+
interface SQLiteDB {
10+
close: () => Promise<void>;
11+
}
12+
13+
describe('Mapped schema', async () => {
14+
const mockSendEmail = vi.fn();
15+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- false positive
16+
let token = '';
17+
18+
const { client, db, auth } = await getTestInstance(
19+
{
20+
emailAndPassword: {
21+
enabled: true,
22+
async sendResetPassword({ url }) {
23+
token = url.split('?')[0]?.split('/').pop() ?? '';
24+
await mockSendEmail();
25+
}
26+
},
27+
plugins: [
28+
emailHarmony({
29+
allowNormalizedSignin: true,
30+
matchers: {
31+
signIn: [emailForget, allEmailSignIn, emailSignUp],
32+
validation: [emailForget, allEmail]
33+
},
34+
schema: {
35+
user: {
36+
fields: {
37+
normalizedEmail: 'normalized_email'
38+
}
39+
}
40+
}
41+
}) as BetterAuthPlugin
42+
]
43+
},
44+
{
45+
disableTestUser: true
46+
}
47+
);
48+
49+
afterAll(async () => {
50+
// TODO: Open PR for better-auth/src/test-utils/test-instance
51+
await (auth.options.database as unknown as SQLiteDB).close();
52+
});
53+
54+
describe('signup', () => {
55+
it('should normalize email', async () => {
56+
const rawEmail = 'new.email+test@googlemail.com';
57+
await client.signUp.email({
58+
email: rawEmail,
59+
password: 'new-password',
60+
name: 'new-name'
61+
});
62+
const userYes = await db.findOne<UserWithNormalizedEmail>({
63+
model: 'user',
64+
where: [
65+
{
66+
field: 'email',
67+
value: rawEmail
68+
}
69+
]
70+
});
71+
// expect(userNo?.email).toBeUndefined();
72+
expect(userYes?.email).toBe(rawEmail);
73+
});
74+
75+
it('should reject temporary emails', async () => {
76+
const rawEmail = 'email@mailinator.com';
77+
const { error } = await client.signUp.email({
78+
email: rawEmail,
79+
password: 'new-password',
80+
name: 'new-name'
81+
});
82+
expect(error).not.toBeNull();
83+
});
84+
85+
it('should prevent signups with email variations', async () => {
86+
const rawEmail = 'test.mail+test1@googlemail.com';
87+
await client.signUp.email({
88+
email: rawEmail,
89+
password: 'new-password',
90+
name: 'new-name'
91+
});
92+
const user = await db.findOne<UserWithNormalizedEmail>({
93+
model: 'user',
94+
where: [
95+
{
96+
field: 'normalizedEmail',
97+
value: 'testmail@gmail.com'
98+
}
99+
]
100+
});
101+
expect(user?.email).toBe(rawEmail);
102+
103+
// Duplicate signup attempt
104+
const { error } = await client.signUp.email({
105+
email: 'testmail+test2@googlemail.com',
106+
password: 'new-password',
107+
name: 'new-name'
108+
});
109+
110+
expect(error?.status).toBe(422);
111+
});
112+
});
113+
});

packages/plugins/src/email/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ export interface UserWithNormalizedEmail extends User {
1111
normalizedEmail?: string | null;
1212
}
1313

14+
export interface EmailHarmonySchema {
15+
user: {
16+
fields: {
17+
/** Map the normalizedEmail field name to a custom value */
18+
normalizedEmail?: string;
19+
};
20+
};
21+
}
22+
1423
export interface EmailHarmonyOptions {
1524
/**
1625
* Allow logging in with any version of the unnormalized email address. Also works for password
@@ -57,6 +66,8 @@ export interface EmailHarmonyOptions {
5766
*/
5867
validation?: Matcher[];
5968
};
69+
/** Pass the `schema` option to customize the field names */
70+
schema?: EmailHarmonySchema;
6071
}
6172

6273
interface Context {
@@ -84,6 +95,7 @@ const emailHarmony = ({
8495
allowNormalizedSignin = false,
8596
validator = validateEmail,
8697
matchers = {},
98+
schema,
8799
normalizer = normalizeEmail
88100
}: EmailHarmonyOptions = {}): BetterAuthPlugin =>
89101
({
@@ -126,6 +138,7 @@ const emailHarmony = ({
126138
fields: {
127139
normalizedEmail: {
128140
type: 'string',
141+
fieldName: schema?.user.fields.normalizedEmail ?? 'normalizedEmail',
129142
required: false,
130143
unique: true,
131144
input: false,

0 commit comments

Comments
 (0)