@startsimpli/auth 0.1.0 → 0.1.2
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/dist/chunk-CDNZRZ7Q.mjs +767 -0
- package/dist/chunk-CDNZRZ7Q.mjs.map +1 -0
- package/dist/chunk-S6J5FYQY.mjs +134 -0
- package/dist/chunk-S6J5FYQY.mjs.map +1 -0
- package/dist/chunk-TA46ASDJ.mjs +37 -0
- package/dist/chunk-TA46ASDJ.mjs.map +1 -0
- package/dist/client/index.d.mts +175 -0
- package/dist/client/index.d.ts +175 -0
- package/dist/client/index.js +858 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +5 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +971 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server/index.d.mts +83 -0
- package/dist/server/index.d.ts +83 -0
- package/dist/server/index.js +242 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +191 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/types/index.d.mts +209 -0
- package/dist/types/index.d.ts +209 -0
- package/dist/types/index.js +43 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +3 -0
- package/dist/types/index.mjs.map +1 -0
- package/package.json +50 -18
- package/src/__tests__/auth-client.test.ts +125 -0
- package/src/__tests__/auth-fetch.test.ts +128 -0
- package/src/__tests__/token-storage.test.ts +61 -0
- package/src/__tests__/validation.test.ts +60 -0
- package/src/client/auth-client.ts +11 -1
- package/src/client/functions.ts +83 -14
- package/src/types/index.ts +100 -0
- package/src/utils/validation.ts +190 -0
package/src/types/index.ts
CHANGED
|
@@ -140,3 +140,103 @@ export function hasRolePermission(
|
|
|
140
140
|
): boolean {
|
|
141
141
|
return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Password reset request payload
|
|
146
|
+
* Initiates password reset flow by sending reset email
|
|
147
|
+
*/
|
|
148
|
+
export interface PasswordResetRequest {
|
|
149
|
+
email: string;
|
|
150
|
+
clientMetadata?: Record<string, any>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Password reset confirmation payload
|
|
155
|
+
* Completes password reset with token and new password
|
|
156
|
+
*/
|
|
157
|
+
export interface PasswordResetConfirm {
|
|
158
|
+
token: string;
|
|
159
|
+
password: string;
|
|
160
|
+
passwordConfirm: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Email verification request payload
|
|
165
|
+
* Verifies user email with token from verification email
|
|
166
|
+
*/
|
|
167
|
+
export interface EmailVerificationRequest {
|
|
168
|
+
token: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Email verification response
|
|
173
|
+
* Returns updated user data after successful verification
|
|
174
|
+
*/
|
|
175
|
+
export interface EmailVerificationResponse {
|
|
176
|
+
detail: string;
|
|
177
|
+
user: {
|
|
178
|
+
id: string;
|
|
179
|
+
email: string;
|
|
180
|
+
isEmailVerified: boolean;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* API error response from Django backend
|
|
186
|
+
* Standard error format for all API errors
|
|
187
|
+
*/
|
|
188
|
+
export interface ApiErrorResponse {
|
|
189
|
+
detail?: string;
|
|
190
|
+
message?: string;
|
|
191
|
+
errors?: Record<string, string[]>;
|
|
192
|
+
code?: string;
|
|
193
|
+
status?: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validation error detail
|
|
198
|
+
* Individual field validation error
|
|
199
|
+
*/
|
|
200
|
+
export interface ValidationError {
|
|
201
|
+
field: string;
|
|
202
|
+
message: string;
|
|
203
|
+
code?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validation errors map
|
|
208
|
+
* Maps field names to error messages
|
|
209
|
+
*/
|
|
210
|
+
export type ValidationErrorsMap = Record<string, string[]>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Password validation error codes
|
|
214
|
+
*/
|
|
215
|
+
export enum PasswordErrorCode {
|
|
216
|
+
TOO_SHORT = 'password_too_short',
|
|
217
|
+
TOO_COMMON = 'password_too_common',
|
|
218
|
+
ENTIRELY_NUMERIC = 'password_entirely_numeric',
|
|
219
|
+
TOO_SIMILAR = 'password_too_similar',
|
|
220
|
+
MISMATCH = 'password_mismatch',
|
|
221
|
+
REQUIRED = 'password_required',
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Email validation error codes
|
|
226
|
+
*/
|
|
227
|
+
export enum EmailErrorCode {
|
|
228
|
+
INVALID_FORMAT = 'email_invalid_format',
|
|
229
|
+
REQUIRED = 'email_required',
|
|
230
|
+
NOT_FOUND = 'email_not_found',
|
|
231
|
+
ALREADY_EXISTS = 'email_already_exists',
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* General validation error codes
|
|
236
|
+
*/
|
|
237
|
+
export enum ValidationErrorCode {
|
|
238
|
+
REQUIRED = 'required',
|
|
239
|
+
INVALID = 'invalid',
|
|
240
|
+
TOO_SHORT = 'too_short',
|
|
241
|
+
TOO_LONG = 'too_long',
|
|
242
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
PasswordErrorCode,
|
|
4
|
+
EmailErrorCode,
|
|
5
|
+
ValidationErrorsMap,
|
|
6
|
+
} from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Email validation schema
|
|
10
|
+
* Matches Django email validation rules
|
|
11
|
+
*/
|
|
12
|
+
export const emailSchema = z
|
|
13
|
+
.string({
|
|
14
|
+
message: EmailErrorCode.REQUIRED,
|
|
15
|
+
})
|
|
16
|
+
.email({
|
|
17
|
+
message: EmailErrorCode.INVALID_FORMAT,
|
|
18
|
+
})
|
|
19
|
+
.min(1, { message: EmailErrorCode.REQUIRED })
|
|
20
|
+
.max(254, { message: 'Email must be less than 254 characters' })
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.trim();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Password validation schema
|
|
26
|
+
* Matches Django password validation rules:
|
|
27
|
+
* - Minimum 8 characters
|
|
28
|
+
* - Cannot be entirely numeric
|
|
29
|
+
* - Cannot be too common (basic check)
|
|
30
|
+
*/
|
|
31
|
+
export const passwordSchema = z
|
|
32
|
+
.string({
|
|
33
|
+
message: PasswordErrorCode.REQUIRED,
|
|
34
|
+
})
|
|
35
|
+
.min(8, { message: PasswordErrorCode.TOO_SHORT })
|
|
36
|
+
.refine((val) => !/^\d+$/.test(val), {
|
|
37
|
+
message: PasswordErrorCode.ENTIRELY_NUMERIC,
|
|
38
|
+
})
|
|
39
|
+
.refine(
|
|
40
|
+
(val) => {
|
|
41
|
+
// Common password check (basic list)
|
|
42
|
+
const commonPasswords = [
|
|
43
|
+
'password',
|
|
44
|
+
'12345678',
|
|
45
|
+
'password1',
|
|
46
|
+
'qwerty123',
|
|
47
|
+
'abc123456',
|
|
48
|
+
];
|
|
49
|
+
return !commonPasswords.includes(val.toLowerCase());
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
message: PasswordErrorCode.TOO_COMMON,
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Password confirmation schema
|
|
58
|
+
* Validates password and confirmation match
|
|
59
|
+
*/
|
|
60
|
+
export const passwordConfirmSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
password: passwordSchema,
|
|
63
|
+
passwordConfirm: z.string(),
|
|
64
|
+
})
|
|
65
|
+
.refine((data) => data.password === data.passwordConfirm, {
|
|
66
|
+
message: PasswordErrorCode.MISMATCH,
|
|
67
|
+
path: ['passwordConfirm'],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Password reset request schema
|
|
72
|
+
*/
|
|
73
|
+
export const passwordResetRequestSchema = z.object({
|
|
74
|
+
email: emailSchema,
|
|
75
|
+
clientMetadata: z.record(z.string(), z.any()).optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Password reset confirm schema
|
|
80
|
+
*/
|
|
81
|
+
export const passwordResetConfirmSchema = z
|
|
82
|
+
.object({
|
|
83
|
+
token: z.string().min(1, { message: 'Token is required' }),
|
|
84
|
+
password: passwordSchema,
|
|
85
|
+
passwordConfirm: z.string(),
|
|
86
|
+
})
|
|
87
|
+
.refine((data) => data.password === data.passwordConfirm, {
|
|
88
|
+
message: PasswordErrorCode.MISMATCH,
|
|
89
|
+
path: ['passwordConfirm'] as const,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Email verification request schema
|
|
94
|
+
*/
|
|
95
|
+
export const emailVerificationRequestSchema = z.object({
|
|
96
|
+
token: z.string().min(1, { message: 'Token is required' }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Convert Zod error to validation errors map
|
|
101
|
+
* Matches Django error response format
|
|
102
|
+
*/
|
|
103
|
+
export function zodErrorToValidationMap(
|
|
104
|
+
error: z.ZodError<any>
|
|
105
|
+
): ValidationErrorsMap {
|
|
106
|
+
const errors: ValidationErrorsMap = {};
|
|
107
|
+
|
|
108
|
+
error.issues.forEach((err) => {
|
|
109
|
+
const field = err.path.join('.');
|
|
110
|
+
if (!errors[field]) {
|
|
111
|
+
errors[field] = [];
|
|
112
|
+
}
|
|
113
|
+
errors[field].push(err.message);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return errors;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate email and return errors
|
|
121
|
+
*/
|
|
122
|
+
export function validateEmail(email: string): string[] {
|
|
123
|
+
const result = emailSchema.safeParse(email);
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
return result.error.issues.map((err) => err.message);
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Validate password and return errors
|
|
132
|
+
*/
|
|
133
|
+
export function validatePassword(password: string): string[] {
|
|
134
|
+
const result = passwordSchema.safeParse(password);
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
return result.error.issues.map((err) => err.message);
|
|
137
|
+
}
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validate password reset request and return errors
|
|
143
|
+
*/
|
|
144
|
+
export function validatePasswordResetRequest(data: unknown): {
|
|
145
|
+
success: boolean;
|
|
146
|
+
errors?: ValidationErrorsMap;
|
|
147
|
+
} {
|
|
148
|
+
const result = passwordResetRequestSchema.safeParse(data);
|
|
149
|
+
if (!result.success) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
errors: zodErrorToValidationMap(result.error),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return { success: true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Validate password reset confirmation and return errors
|
|
160
|
+
*/
|
|
161
|
+
export function validatePasswordResetConfirm(data: unknown): {
|
|
162
|
+
success: boolean;
|
|
163
|
+
errors?: ValidationErrorsMap;
|
|
164
|
+
} {
|
|
165
|
+
const result = passwordResetConfirmSchema.safeParse(data);
|
|
166
|
+
if (!result.success) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
errors: zodErrorToValidationMap(result.error),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return { success: true };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Validate email verification request and return errors
|
|
177
|
+
*/
|
|
178
|
+
export function validateEmailVerificationRequest(data: unknown): {
|
|
179
|
+
success: boolean;
|
|
180
|
+
errors?: ValidationErrorsMap;
|
|
181
|
+
} {
|
|
182
|
+
const result = emailVerificationRequestSchema.safeParse(data);
|
|
183
|
+
if (!result.success) {
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
errors: zodErrorToValidationMap(result.error),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return { success: true };
|
|
190
|
+
}
|