Skip to content

Commit 73d260a

Browse files
committed
♻️ Separate form init and authorization (#2446)
1 parent c084500 commit 73d260a

File tree

6 files changed

+748
-569
lines changed

6 files changed

+748
-569
lines changed

src/lib/utils/auth_forms.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { redirect } from '@sveltejs/kit';
2+
3+
import { superValidate } from 'sveltekit-superforms/server';
4+
import { zod } from 'sveltekit-superforms/adapters';
5+
6+
import { TEMPORARY_REDIRECT, SEE_OTHER } from '$lib/constants/http-response-status-codes';
7+
import { HOME_PAGE } from '$lib/constants/navbar-links';
8+
import { authSchema } from '$lib/zod/schema';
9+
10+
/**
11+
* Initialize authentication form pages (login/signup)
12+
* Redirects to home page if already logged in,
13+
* otherwise initializes the authentication form for unauthenticated users
14+
*/
15+
export const initializeAuthForm = async (locals: App.Locals) => {
16+
const session = await locals.auth.validate();
17+
18+
if (session) {
19+
redirect(SEE_OTHER, HOME_PAGE);
20+
}
21+
22+
return await createAuthFormWithFallback();
23+
};
24+
25+
/**
26+
* Create authentication form with comprehensive fallback handling
27+
* Tries multiple strategies until one succeeds
28+
*/
29+
export const createAuthFormWithFallback = async () => {
30+
for (const strategy of formCreationStrategies) {
31+
try {
32+
const result = await strategy.run();
33+
34+
return result;
35+
} catch (error) {
36+
if (import.meta.env.DEV) {
37+
console.warn(`Failed to ${strategy.name}`);
38+
39+
if (error instanceof Error) {
40+
console.warn('Error:', error.message);
41+
}
42+
}
43+
}
44+
}
45+
46+
// This should never be reached due to manual creation strategy
47+
throw new Error('Failed to create form for authentication.');
48+
};
49+
50+
/**
51+
* Form creation strategies in order of preference
52+
* Each strategy attempts a different approach to create a valid form
53+
*
54+
* See:
55+
* https://superforms.rocks/migration-v2#supervalidate
56+
* https://superforms.rocks/concepts/client-validation
57+
* https://superforms.rocks/api#supervalidate-options
58+
*/
59+
const formCreationStrategies = [
60+
{
61+
name: '(Basic case) Use standard superValidate',
62+
async run() {
63+
const form = await superValidate(zod(authSchema));
64+
return { form: { ...form, message: '' } };
65+
},
66+
},
67+
{
68+
name: 'Create form by manually defining structure',
69+
async run() {
70+
const defaultForm = {
71+
valid: true,
72+
posted: false,
73+
errors: {},
74+
message: '',
75+
...createBaseAuthForm(),
76+
};
77+
78+
return { form: { ...defaultForm, message: '' } };
79+
},
80+
},
81+
];
82+
83+
/**
84+
* Validate authentication form data with comprehensive fallback handling
85+
* Tries multiple strategies until one succeeds
86+
*
87+
* @param request - The incoming request containing form data
88+
* @returns The validated form object
89+
*/
90+
export const validateAuthFormWithFallback = async (request: Request) => {
91+
for (const strategy of formValidationStrategies) {
92+
try {
93+
const result = await strategy.run(request);
94+
95+
return result.form;
96+
} catch (error) {
97+
if (import.meta.env.DEV) {
98+
console.warn(`Failed to ${strategy.name}`);
99+
100+
if (error instanceof Error) {
101+
console.warn('Error:', error.message);
102+
}
103+
}
104+
}
105+
}
106+
107+
// This should never be reached due to fallback strategy
108+
throw new Error('Failed to validate form for authentication.');
109+
};
110+
111+
/**
112+
* Form validation strategies for action handlers
113+
* Each strategy attempts a different approach to validate form data from requests
114+
*/
115+
const formValidationStrategies = [
116+
{
117+
name: '(Basic Case) Use standard superValidate with request',
118+
async run(request: Request) {
119+
const form = await superValidate(request, zod(authSchema));
120+
return { form: { ...form, message: '' } };
121+
},
122+
},
123+
{
124+
name: 'Use zod adapter explicitly',
125+
async run(request: Request) {
126+
const zodAdapter = zod(authSchema);
127+
const form = await superValidate(request, zodAdapter);
128+
return { form: { ...form, message: '' } };
129+
},
130+
},
131+
{
132+
name: 'Create fallback form manually',
133+
async run(_request: Request) {
134+
// Create a fallback form with error state
135+
// This maintains consistency with other strategies by returning { form }
136+
const fallbackForm = {
137+
valid: false,
138+
posted: true,
139+
errors: { _form: ['ログインできませんでした。'] },
140+
message: 'サーバでエラーが発生しました。本サービスの開発・運営チームまでご連絡ください。',
141+
...createBaseAuthForm(),
142+
};
143+
144+
return { form: fallbackForm };
145+
},
146+
},
147+
];
148+
149+
/**
150+
* Common form structure for authentication forms
151+
* Contains constraints and shape definitions used across different form strategies
152+
*/
153+
const createBaseAuthForm = () => ({
154+
id: 'error-fallback-form-' + crypto.randomUUID(), // Note: Use only client-side validation
155+
data: { username: '', password: '' },
156+
constraints: {
157+
username: { minlength: 3, maxlength: 24, required: true, pattern: '[\\w]*' },
158+
password: {
159+
minlength: 8,
160+
maxlength: 128,
161+
required: true,
162+
pattern: '(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)[a-zA-Z\\d]{8,128}',
163+
},
164+
},
165+
shape: {
166+
username: { type: 'string' },
167+
password: { type: 'string' },
168+
},
169+
});

0 commit comments

Comments
 (0)