@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
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { isTokenExpired, decodeToken } from '../chunk-S6J5FYQY.mjs';
|
|
2
|
+
import { hasRolePermission } from '../chunk-TA46ASDJ.mjs';
|
|
3
|
+
import { cookies } from 'next/headers';
|
|
4
|
+
import { NextResponse } from 'next/server';
|
|
5
|
+
|
|
6
|
+
async function getServerSession(apiBaseUrl) {
|
|
7
|
+
const cookieStore = await cookies();
|
|
8
|
+
const accessToken = cookieStore.get("access_token")?.value;
|
|
9
|
+
if (!accessToken) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
if (isTokenExpired(accessToken)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const user = await fetchUser(apiBaseUrl, accessToken);
|
|
17
|
+
const payload = decodeToken(accessToken);
|
|
18
|
+
if (!payload) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
user,
|
|
23
|
+
accessToken,
|
|
24
|
+
expiresAt: payload.exp * 1e3
|
|
25
|
+
};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Failed to get server session:", error);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function fetchUser(apiBaseUrl, accessToken) {
|
|
32
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/auth/me/`, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${accessToken}`
|
|
35
|
+
},
|
|
36
|
+
cache: "no-store"
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error("Failed to fetch user");
|
|
40
|
+
}
|
|
41
|
+
return response.json();
|
|
42
|
+
}
|
|
43
|
+
async function validateSession(apiBaseUrl) {
|
|
44
|
+
const session = await getServerSession(apiBaseUrl);
|
|
45
|
+
return session?.user || null;
|
|
46
|
+
}
|
|
47
|
+
async function requireAuth(apiBaseUrl) {
|
|
48
|
+
const session = await getServerSession(apiBaseUrl);
|
|
49
|
+
if (!session) {
|
|
50
|
+
throw new Error("Unauthorized");
|
|
51
|
+
}
|
|
52
|
+
return session;
|
|
53
|
+
}
|
|
54
|
+
async function refreshServerToken(apiBaseUrl) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/auth/token/refresh/`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json"
|
|
60
|
+
},
|
|
61
|
+
credentials: "include"
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
return data.access;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("Token refresh failed:", error);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function createAuthMiddleware(config) {
|
|
74
|
+
const {
|
|
75
|
+
apiBaseUrl,
|
|
76
|
+
publicPaths = ["/login", "/register", "/forgot-password"],
|
|
77
|
+
loginPath = "/login"
|
|
78
|
+
} = config;
|
|
79
|
+
return async function authMiddleware(request) {
|
|
80
|
+
const { pathname } = request.nextUrl;
|
|
81
|
+
const isPublicPath = publicPaths.some(
|
|
82
|
+
(path) => pathname.startsWith(path)
|
|
83
|
+
);
|
|
84
|
+
if (isPublicPath) {
|
|
85
|
+
return NextResponse.next();
|
|
86
|
+
}
|
|
87
|
+
const accessToken = request.cookies.get("access_token")?.value;
|
|
88
|
+
if (!accessToken || isTokenExpired(accessToken)) {
|
|
89
|
+
const url = request.nextUrl.clone();
|
|
90
|
+
url.pathname = loginPath;
|
|
91
|
+
url.searchParams.set("from", pathname);
|
|
92
|
+
return NextResponse.redirect(url);
|
|
93
|
+
}
|
|
94
|
+
return NextResponse.next();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function hasValidToken(request) {
|
|
98
|
+
const accessToken = request.cookies.get("access_token")?.value;
|
|
99
|
+
if (!accessToken) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return !isTokenExpired(accessToken);
|
|
103
|
+
}
|
|
104
|
+
function getRequestToken(request) {
|
|
105
|
+
const accessToken = request.cookies.get("access_token")?.value;
|
|
106
|
+
if (!accessToken) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (isTokenExpired(accessToken)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return accessToken;
|
|
113
|
+
}
|
|
114
|
+
function getTokenFromRequest(req) {
|
|
115
|
+
const authHeader = req.headers.get("authorization");
|
|
116
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
117
|
+
const token = authHeader.slice(7).trim();
|
|
118
|
+
if (token) return token;
|
|
119
|
+
}
|
|
120
|
+
const cookieHeader = req.headers.get("cookie");
|
|
121
|
+
if (cookieHeader) {
|
|
122
|
+
const match = cookieHeader.split(";").map((part) => part.trim()).find((part) => part.startsWith("access_token="));
|
|
123
|
+
if (match) {
|
|
124
|
+
const token = match.slice("access_token=".length).trim();
|
|
125
|
+
if (token) return token;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
async function requireAuthGuard(request) {
|
|
131
|
+
const token = getRequestToken(request);
|
|
132
|
+
if (!token) {
|
|
133
|
+
return {
|
|
134
|
+
authorized: false,
|
|
135
|
+
response: NextResponse.json(
|
|
136
|
+
{ error: "Unauthorized" },
|
|
137
|
+
{ status: 401 }
|
|
138
|
+
)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
authorized: true,
|
|
143
|
+
token
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function requireRoleGuard(request, requiredRole, getUserRole) {
|
|
147
|
+
const authResult = await requireAuthGuard(request);
|
|
148
|
+
if (!authResult.authorized) {
|
|
149
|
+
return authResult;
|
|
150
|
+
}
|
|
151
|
+
const userRole = await getUserRole();
|
|
152
|
+
if (!userRole || !hasRolePermission(userRole, requiredRole)) {
|
|
153
|
+
return {
|
|
154
|
+
authorized: false,
|
|
155
|
+
response: NextResponse.json(
|
|
156
|
+
{ error: "Forbidden" },
|
|
157
|
+
{ status: 403 }
|
|
158
|
+
)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
authorized: true,
|
|
163
|
+
token: authResult.token
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function withAuth(handler) {
|
|
167
|
+
return async (request) => {
|
|
168
|
+
const guardResult = await requireAuthGuard(request);
|
|
169
|
+
if (!guardResult.authorized) {
|
|
170
|
+
return guardResult.response;
|
|
171
|
+
}
|
|
172
|
+
return handler(request, guardResult.token);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function withRole(requiredRole, getUserRole, handler) {
|
|
176
|
+
return async (request) => {
|
|
177
|
+
const guardResult = await requireRoleGuard(
|
|
178
|
+
request,
|
|
179
|
+
requiredRole,
|
|
180
|
+
getUserRole
|
|
181
|
+
);
|
|
182
|
+
if (!guardResult.authorized) {
|
|
183
|
+
return guardResult.response;
|
|
184
|
+
}
|
|
185
|
+
return handler(request, guardResult.token);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { createAuthMiddleware, getRequestToken, getServerSession, getTokenFromRequest, hasValidToken, refreshServerToken, requireAuth, requireAuthGuard, requireRoleGuard, validateSession, withAuth, withRole };
|
|
190
|
+
//# sourceMappingURL=index.mjs.map
|
|
191
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/session.ts","../../src/server/middleware.ts","../../src/server/guards.ts"],"names":["NextResponse"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Server-side session validation\n * For Next.js API routes and server components\n */\n\nimport { cookies } from 'next/headers';\nimport type { Session, AuthUser, TokenPayload } from '../types';\nimport { decodeToken, isTokenExpired } from '../utils';\n\n/**\n * Get session from server-side cookies and headers\n */\nexport async function getServerSession(\n apiBaseUrl: string\n): Promise<Session | null> {\n const cookieStore = await cookies();\n const accessToken = cookieStore.get('access_token')?.value;\n\n if (!accessToken) {\n return null;\n }\n\n if (isTokenExpired(accessToken)) {\n return null;\n }\n\n try {\n const user = await fetchUser(apiBaseUrl, accessToken);\n const payload = decodeToken(accessToken);\n\n if (!payload) {\n return null;\n }\n\n return {\n user,\n accessToken,\n expiresAt: payload.exp * 1000,\n };\n } catch (error) {\n console.error('Failed to get server session:', error);\n return null;\n }\n}\n\n/**\n * Fetch user data from Django backend\n */\nasync function fetchUser(\n apiBaseUrl: string,\n accessToken: string\n): Promise<AuthUser> {\n const response = await fetch(`${apiBaseUrl}/api/v1/auth/me/`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n cache: 'no-store',\n });\n\n if (!response.ok) {\n throw new Error('Failed to fetch user');\n }\n\n return response.json();\n}\n\n/**\n * Validate session and return user or null\n */\nexport async function validateSession(\n apiBaseUrl: string\n): Promise<AuthUser | null> {\n const session = await getServerSession(apiBaseUrl);\n return session?.user || null;\n}\n\n/**\n * Require authenticated session (throws if not authenticated)\n */\nexport async function requireAuth(apiBaseUrl: string): Promise<Session> {\n const session = await getServerSession(apiBaseUrl);\n\n if (!session) {\n throw new Error('Unauthorized');\n }\n\n return session;\n}\n\n/**\n * Refresh access token using refresh token cookie\n */\nexport async function refreshServerToken(\n apiBaseUrl: string\n): Promise<string | null> {\n try {\n const response = await fetch(`${apiBaseUrl}/api/v1/auth/token/refresh/`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n credentials: 'include',\n });\n\n if (!response.ok) {\n return null;\n }\n\n const data = await response.json();\n return data.access;\n } catch (error) {\n console.error('Token refresh failed:', error);\n return null;\n }\n}\n","/**\n * Next.js middleware helpers for authentication\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { isTokenExpired } from '../utils';\n\nexport interface AuthMiddlewareConfig {\n apiBaseUrl: string;\n publicPaths?: string[];\n loginPath?: string;\n}\n\n/**\n * Create auth middleware for Next.js\n */\nexport function createAuthMiddleware(config: AuthMiddlewareConfig) {\n const {\n apiBaseUrl,\n publicPaths = ['/login', '/register', '/forgot-password'],\n loginPath = '/login',\n } = config;\n\n return async function authMiddleware(request: NextRequest) {\n const { pathname } = request.nextUrl;\n\n const isPublicPath = publicPaths.some((path) =>\n pathname.startsWith(path)\n );\n\n if (isPublicPath) {\n return NextResponse.next();\n }\n\n const accessToken = request.cookies.get('access_token')?.value;\n\n if (!accessToken || isTokenExpired(accessToken)) {\n const url = request.nextUrl.clone();\n url.pathname = loginPath;\n url.searchParams.set('from', pathname);\n return NextResponse.redirect(url);\n }\n\n return NextResponse.next();\n };\n}\n\n/**\n * Check if request has valid auth token\n */\nexport function hasValidToken(request: NextRequest): boolean {\n const accessToken = request.cookies.get('access_token')?.value;\n\n if (!accessToken) {\n return false;\n }\n\n return !isTokenExpired(accessToken);\n}\n\n/**\n * Get access token from request\n */\nexport function getRequestToken(request: NextRequest): string | null {\n const accessToken = request.cookies.get('access_token')?.value;\n\n if (!accessToken) {\n return null;\n }\n\n if (isTokenExpired(accessToken)) {\n return null;\n }\n\n return accessToken;\n}\n\n/**\n * Extract bearer token from a standard Request (Authorization header or access_token cookie).\n * Framework-agnostic — works with any Request-compatible object (Next.js, Node, edge runtimes).\n * Returns the raw token string without expiry validation.\n */\nexport function getTokenFromRequest(req: Request): string | undefined {\n // Authorization: Bearer <token>\n const authHeader = req.headers.get('authorization');\n if (authHeader?.startsWith('Bearer ')) {\n const token = authHeader.slice(7).trim();\n if (token) return token;\n }\n\n // Fallback: access_token cookie (parsed from Cookie header)\n const cookieHeader = req.headers.get('cookie');\n if (cookieHeader) {\n const match = cookieHeader\n .split(';')\n .map((part) => part.trim())\n .find((part) => part.startsWith('access_token='));\n\n if (match) {\n const token = match.slice('access_token='.length).trim();\n if (token) return token;\n }\n }\n\n return undefined;\n}\n","/**\n * Auth guards for API routes\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport type { CompanyRole } from '../types';\nimport { hasRolePermission } from '../types';\nimport { getRequestToken } from './middleware';\n\n/**\n * Auth guard result\n */\nexport interface GuardResult {\n authorized: boolean;\n response?: NextResponse;\n token?: string;\n}\n\n/**\n * Require authentication for API route\n */\nexport async function requireAuthGuard(\n request: NextRequest\n): Promise<GuardResult> {\n const token = getRequestToken(request);\n\n if (!token) {\n return {\n authorized: false,\n response: NextResponse.json(\n { error: 'Unauthorized' },\n { status: 401 }\n ),\n };\n }\n\n return {\n authorized: true,\n token,\n };\n}\n\n/**\n * Require specific role for API route\n */\nexport async function requireRoleGuard(\n request: NextRequest,\n requiredRole: CompanyRole,\n getUserRole: () => Promise<CompanyRole | null>\n): Promise<GuardResult> {\n const authResult = await requireAuthGuard(request);\n\n if (!authResult.authorized) {\n return authResult;\n }\n\n const userRole = await getUserRole();\n\n if (!userRole || !hasRolePermission(userRole, requiredRole)) {\n return {\n authorized: false,\n response: NextResponse.json(\n { error: 'Forbidden' },\n { status: 403 }\n ),\n };\n }\n\n return {\n authorized: true,\n token: authResult.token,\n };\n}\n\n/**\n * Higher-order function to wrap API route with auth guard\n */\nexport function withAuth<T = any>(\n handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>\n) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const guardResult = await requireAuthGuard(request);\n\n if (!guardResult.authorized) {\n return guardResult.response!;\n }\n\n return handler(request, guardResult.token!);\n };\n}\n\n/**\n * Higher-order function to wrap API route with role guard\n */\nexport function withRole<T = any>(\n requiredRole: CompanyRole,\n getUserRole: () => Promise<CompanyRole | null>,\n handler: (request: NextRequest, token: string) => Promise<NextResponse<T>>\n) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const guardResult = await requireRoleGuard(\n request,\n requiredRole,\n getUserRole\n );\n\n if (!guardResult.authorized) {\n return guardResult.response!;\n }\n\n return handler(request, guardResult.token!);\n };\n}\n"]}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication types for StartSimpli apps
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generic token pair (access + optional refresh)
|
|
6
|
+
*/
|
|
7
|
+
interface TokenPair {
|
|
8
|
+
access: string;
|
|
9
|
+
refresh?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generic decoded JWT payload — framework and backend agnostic
|
|
13
|
+
*/
|
|
14
|
+
interface DecodedToken {
|
|
15
|
+
sub?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
exp?: number;
|
|
18
|
+
iat?: number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generic auth session — framework agnostic
|
|
23
|
+
*/
|
|
24
|
+
interface AuthSession {
|
|
25
|
+
user: {
|
|
26
|
+
id: string;
|
|
27
|
+
email: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
accessToken: string;
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* User profile from Django backend
|
|
35
|
+
*/
|
|
36
|
+
interface AuthUser {
|
|
37
|
+
id: string;
|
|
38
|
+
email: string;
|
|
39
|
+
firstName: string;
|
|
40
|
+
lastName: string;
|
|
41
|
+
isEmailVerified: boolean;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
updatedAt: string;
|
|
44
|
+
companies?: Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
role: 'owner' | 'admin' | 'member' | 'viewer';
|
|
48
|
+
}>;
|
|
49
|
+
currentCompanyId?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* JWT token payload structure
|
|
53
|
+
*/
|
|
54
|
+
interface TokenPayload {
|
|
55
|
+
token_type: 'access';
|
|
56
|
+
exp: number;
|
|
57
|
+
iat: number;
|
|
58
|
+
jti: string;
|
|
59
|
+
user_id: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Session data stored in client
|
|
63
|
+
*/
|
|
64
|
+
interface Session {
|
|
65
|
+
user: AuthUser;
|
|
66
|
+
accessToken: string;
|
|
67
|
+
expiresAt: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Login response from Django backend
|
|
71
|
+
*/
|
|
72
|
+
interface LoginResponse {
|
|
73
|
+
access: string;
|
|
74
|
+
user: AuthUser;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Token refresh response
|
|
78
|
+
*/
|
|
79
|
+
interface RefreshResponse {
|
|
80
|
+
access: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Permission check result
|
|
84
|
+
*/
|
|
85
|
+
interface PermissionCheck {
|
|
86
|
+
hasPermission: boolean;
|
|
87
|
+
reason?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Auth configuration options
|
|
91
|
+
*/
|
|
92
|
+
interface AuthConfig {
|
|
93
|
+
apiBaseUrl: string;
|
|
94
|
+
tokenRefreshInterval?: number;
|
|
95
|
+
onSessionExpired?: () => void;
|
|
96
|
+
onUnauthorized?: () => void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Auth state for React context
|
|
100
|
+
*/
|
|
101
|
+
interface AuthState {
|
|
102
|
+
session: Session | null;
|
|
103
|
+
isLoading: boolean;
|
|
104
|
+
isAuthenticated: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Company role hierarchy
|
|
108
|
+
*/
|
|
109
|
+
type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer';
|
|
110
|
+
/**
|
|
111
|
+
* Role hierarchy map (higher number = more permissions)
|
|
112
|
+
*/
|
|
113
|
+
declare const ROLE_HIERARCHY: Record<CompanyRole, number>;
|
|
114
|
+
/**
|
|
115
|
+
* Check if role has sufficient permissions
|
|
116
|
+
*/
|
|
117
|
+
declare function hasRolePermission(userRole: CompanyRole, requiredRole: CompanyRole): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Password reset request payload
|
|
120
|
+
* Initiates password reset flow by sending reset email
|
|
121
|
+
*/
|
|
122
|
+
interface PasswordResetRequest {
|
|
123
|
+
email: string;
|
|
124
|
+
clientMetadata?: Record<string, any>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Password reset confirmation payload
|
|
128
|
+
* Completes password reset with token and new password
|
|
129
|
+
*/
|
|
130
|
+
interface PasswordResetConfirm {
|
|
131
|
+
token: string;
|
|
132
|
+
password: string;
|
|
133
|
+
passwordConfirm: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Email verification request payload
|
|
137
|
+
* Verifies user email with token from verification email
|
|
138
|
+
*/
|
|
139
|
+
interface EmailVerificationRequest {
|
|
140
|
+
token: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Email verification response
|
|
144
|
+
* Returns updated user data after successful verification
|
|
145
|
+
*/
|
|
146
|
+
interface EmailVerificationResponse {
|
|
147
|
+
detail: string;
|
|
148
|
+
user: {
|
|
149
|
+
id: string;
|
|
150
|
+
email: string;
|
|
151
|
+
isEmailVerified: boolean;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* API error response from Django backend
|
|
156
|
+
* Standard error format for all API errors
|
|
157
|
+
*/
|
|
158
|
+
interface ApiErrorResponse {
|
|
159
|
+
detail?: string;
|
|
160
|
+
message?: string;
|
|
161
|
+
errors?: Record<string, string[]>;
|
|
162
|
+
code?: string;
|
|
163
|
+
status?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validation error detail
|
|
167
|
+
* Individual field validation error
|
|
168
|
+
*/
|
|
169
|
+
interface ValidationError {
|
|
170
|
+
field: string;
|
|
171
|
+
message: string;
|
|
172
|
+
code?: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validation errors map
|
|
176
|
+
* Maps field names to error messages
|
|
177
|
+
*/
|
|
178
|
+
type ValidationErrorsMap = Record<string, string[]>;
|
|
179
|
+
/**
|
|
180
|
+
* Password validation error codes
|
|
181
|
+
*/
|
|
182
|
+
declare enum PasswordErrorCode {
|
|
183
|
+
TOO_SHORT = "password_too_short",
|
|
184
|
+
TOO_COMMON = "password_too_common",
|
|
185
|
+
ENTIRELY_NUMERIC = "password_entirely_numeric",
|
|
186
|
+
TOO_SIMILAR = "password_too_similar",
|
|
187
|
+
MISMATCH = "password_mismatch",
|
|
188
|
+
REQUIRED = "password_required"
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Email validation error codes
|
|
192
|
+
*/
|
|
193
|
+
declare enum EmailErrorCode {
|
|
194
|
+
INVALID_FORMAT = "email_invalid_format",
|
|
195
|
+
REQUIRED = "email_required",
|
|
196
|
+
NOT_FOUND = "email_not_found",
|
|
197
|
+
ALREADY_EXISTS = "email_already_exists"
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* General validation error codes
|
|
201
|
+
*/
|
|
202
|
+
declare enum ValidationErrorCode {
|
|
203
|
+
REQUIRED = "required",
|
|
204
|
+
INVALID = "invalid",
|
|
205
|
+
TOO_SHORT = "too_short",
|
|
206
|
+
TOO_LONG = "too_long"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { type ApiErrorResponse, type AuthConfig, type AuthSession, type AuthState, type AuthUser, type CompanyRole, type DecodedToken, EmailErrorCode, type EmailVerificationRequest, type EmailVerificationResponse, type LoginResponse, PasswordErrorCode, type PasswordResetConfirm, type PasswordResetRequest, type PermissionCheck, ROLE_HIERARCHY, type RefreshResponse, type Session, type TokenPair, type TokenPayload, type ValidationError, ValidationErrorCode, type ValidationErrorsMap, hasRolePermission };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication types for StartSimpli apps
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generic token pair (access + optional refresh)
|
|
6
|
+
*/
|
|
7
|
+
interface TokenPair {
|
|
8
|
+
access: string;
|
|
9
|
+
refresh?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generic decoded JWT payload — framework and backend agnostic
|
|
13
|
+
*/
|
|
14
|
+
interface DecodedToken {
|
|
15
|
+
sub?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
exp?: number;
|
|
18
|
+
iat?: number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generic auth session — framework agnostic
|
|
23
|
+
*/
|
|
24
|
+
interface AuthSession {
|
|
25
|
+
user: {
|
|
26
|
+
id: string;
|
|
27
|
+
email: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
accessToken: string;
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* User profile from Django backend
|
|
35
|
+
*/
|
|
36
|
+
interface AuthUser {
|
|
37
|
+
id: string;
|
|
38
|
+
email: string;
|
|
39
|
+
firstName: string;
|
|
40
|
+
lastName: string;
|
|
41
|
+
isEmailVerified: boolean;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
updatedAt: string;
|
|
44
|
+
companies?: Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
role: 'owner' | 'admin' | 'member' | 'viewer';
|
|
48
|
+
}>;
|
|
49
|
+
currentCompanyId?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* JWT token payload structure
|
|
53
|
+
*/
|
|
54
|
+
interface TokenPayload {
|
|
55
|
+
token_type: 'access';
|
|
56
|
+
exp: number;
|
|
57
|
+
iat: number;
|
|
58
|
+
jti: string;
|
|
59
|
+
user_id: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Session data stored in client
|
|
63
|
+
*/
|
|
64
|
+
interface Session {
|
|
65
|
+
user: AuthUser;
|
|
66
|
+
accessToken: string;
|
|
67
|
+
expiresAt: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Login response from Django backend
|
|
71
|
+
*/
|
|
72
|
+
interface LoginResponse {
|
|
73
|
+
access: string;
|
|
74
|
+
user: AuthUser;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Token refresh response
|
|
78
|
+
*/
|
|
79
|
+
interface RefreshResponse {
|
|
80
|
+
access: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Permission check result
|
|
84
|
+
*/
|
|
85
|
+
interface PermissionCheck {
|
|
86
|
+
hasPermission: boolean;
|
|
87
|
+
reason?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Auth configuration options
|
|
91
|
+
*/
|
|
92
|
+
interface AuthConfig {
|
|
93
|
+
apiBaseUrl: string;
|
|
94
|
+
tokenRefreshInterval?: number;
|
|
95
|
+
onSessionExpired?: () => void;
|
|
96
|
+
onUnauthorized?: () => void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Auth state for React context
|
|
100
|
+
*/
|
|
101
|
+
interface AuthState {
|
|
102
|
+
session: Session | null;
|
|
103
|
+
isLoading: boolean;
|
|
104
|
+
isAuthenticated: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Company role hierarchy
|
|
108
|
+
*/
|
|
109
|
+
type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer';
|
|
110
|
+
/**
|
|
111
|
+
* Role hierarchy map (higher number = more permissions)
|
|
112
|
+
*/
|
|
113
|
+
declare const ROLE_HIERARCHY: Record<CompanyRole, number>;
|
|
114
|
+
/**
|
|
115
|
+
* Check if role has sufficient permissions
|
|
116
|
+
*/
|
|
117
|
+
declare function hasRolePermission(userRole: CompanyRole, requiredRole: CompanyRole): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Password reset request payload
|
|
120
|
+
* Initiates password reset flow by sending reset email
|
|
121
|
+
*/
|
|
122
|
+
interface PasswordResetRequest {
|
|
123
|
+
email: string;
|
|
124
|
+
clientMetadata?: Record<string, any>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Password reset confirmation payload
|
|
128
|
+
* Completes password reset with token and new password
|
|
129
|
+
*/
|
|
130
|
+
interface PasswordResetConfirm {
|
|
131
|
+
token: string;
|
|
132
|
+
password: string;
|
|
133
|
+
passwordConfirm: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Email verification request payload
|
|
137
|
+
* Verifies user email with token from verification email
|
|
138
|
+
*/
|
|
139
|
+
interface EmailVerificationRequest {
|
|
140
|
+
token: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Email verification response
|
|
144
|
+
* Returns updated user data after successful verification
|
|
145
|
+
*/
|
|
146
|
+
interface EmailVerificationResponse {
|
|
147
|
+
detail: string;
|
|
148
|
+
user: {
|
|
149
|
+
id: string;
|
|
150
|
+
email: string;
|
|
151
|
+
isEmailVerified: boolean;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* API error response from Django backend
|
|
156
|
+
* Standard error format for all API errors
|
|
157
|
+
*/
|
|
158
|
+
interface ApiErrorResponse {
|
|
159
|
+
detail?: string;
|
|
160
|
+
message?: string;
|
|
161
|
+
errors?: Record<string, string[]>;
|
|
162
|
+
code?: string;
|
|
163
|
+
status?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validation error detail
|
|
167
|
+
* Individual field validation error
|
|
168
|
+
*/
|
|
169
|
+
interface ValidationError {
|
|
170
|
+
field: string;
|
|
171
|
+
message: string;
|
|
172
|
+
code?: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validation errors map
|
|
176
|
+
* Maps field names to error messages
|
|
177
|
+
*/
|
|
178
|
+
type ValidationErrorsMap = Record<string, string[]>;
|
|
179
|
+
/**
|
|
180
|
+
* Password validation error codes
|
|
181
|
+
*/
|
|
182
|
+
declare enum PasswordErrorCode {
|
|
183
|
+
TOO_SHORT = "password_too_short",
|
|
184
|
+
TOO_COMMON = "password_too_common",
|
|
185
|
+
ENTIRELY_NUMERIC = "password_entirely_numeric",
|
|
186
|
+
TOO_SIMILAR = "password_too_similar",
|
|
187
|
+
MISMATCH = "password_mismatch",
|
|
188
|
+
REQUIRED = "password_required"
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Email validation error codes
|
|
192
|
+
*/
|
|
193
|
+
declare enum EmailErrorCode {
|
|
194
|
+
INVALID_FORMAT = "email_invalid_format",
|
|
195
|
+
REQUIRED = "email_required",
|
|
196
|
+
NOT_FOUND = "email_not_found",
|
|
197
|
+
ALREADY_EXISTS = "email_already_exists"
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* General validation error codes
|
|
201
|
+
*/
|
|
202
|
+
declare enum ValidationErrorCode {
|
|
203
|
+
REQUIRED = "required",
|
|
204
|
+
INVALID = "invalid",
|
|
205
|
+
TOO_SHORT = "too_short",
|
|
206
|
+
TOO_LONG = "too_long"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { type ApiErrorResponse, type AuthConfig, type AuthSession, type AuthState, type AuthUser, type CompanyRole, type DecodedToken, EmailErrorCode, type EmailVerificationRequest, type EmailVerificationResponse, type LoginResponse, PasswordErrorCode, type PasswordResetConfirm, type PasswordResetRequest, type PermissionCheck, ROLE_HIERARCHY, type RefreshResponse, type Session, type TokenPair, type TokenPayload, type ValidationError, ValidationErrorCode, type ValidationErrorsMap, hasRolePermission };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/types/index.ts
|
|
4
|
+
var ROLE_HIERARCHY = {
|
|
5
|
+
owner: 4,
|
|
6
|
+
admin: 3,
|
|
7
|
+
member: 2,
|
|
8
|
+
viewer: 1
|
|
9
|
+
};
|
|
10
|
+
function hasRolePermission(userRole, requiredRole) {
|
|
11
|
+
return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];
|
|
12
|
+
}
|
|
13
|
+
var PasswordErrorCode = /* @__PURE__ */ ((PasswordErrorCode2) => {
|
|
14
|
+
PasswordErrorCode2["TOO_SHORT"] = "password_too_short";
|
|
15
|
+
PasswordErrorCode2["TOO_COMMON"] = "password_too_common";
|
|
16
|
+
PasswordErrorCode2["ENTIRELY_NUMERIC"] = "password_entirely_numeric";
|
|
17
|
+
PasswordErrorCode2["TOO_SIMILAR"] = "password_too_similar";
|
|
18
|
+
PasswordErrorCode2["MISMATCH"] = "password_mismatch";
|
|
19
|
+
PasswordErrorCode2["REQUIRED"] = "password_required";
|
|
20
|
+
return PasswordErrorCode2;
|
|
21
|
+
})(PasswordErrorCode || {});
|
|
22
|
+
var EmailErrorCode = /* @__PURE__ */ ((EmailErrorCode2) => {
|
|
23
|
+
EmailErrorCode2["INVALID_FORMAT"] = "email_invalid_format";
|
|
24
|
+
EmailErrorCode2["REQUIRED"] = "email_required";
|
|
25
|
+
EmailErrorCode2["NOT_FOUND"] = "email_not_found";
|
|
26
|
+
EmailErrorCode2["ALREADY_EXISTS"] = "email_already_exists";
|
|
27
|
+
return EmailErrorCode2;
|
|
28
|
+
})(EmailErrorCode || {});
|
|
29
|
+
var ValidationErrorCode = /* @__PURE__ */ ((ValidationErrorCode2) => {
|
|
30
|
+
ValidationErrorCode2["REQUIRED"] = "required";
|
|
31
|
+
ValidationErrorCode2["INVALID"] = "invalid";
|
|
32
|
+
ValidationErrorCode2["TOO_SHORT"] = "too_short";
|
|
33
|
+
ValidationErrorCode2["TOO_LONG"] = "too_long";
|
|
34
|
+
return ValidationErrorCode2;
|
|
35
|
+
})(ValidationErrorCode || {});
|
|
36
|
+
|
|
37
|
+
exports.EmailErrorCode = EmailErrorCode;
|
|
38
|
+
exports.PasswordErrorCode = PasswordErrorCode;
|
|
39
|
+
exports.ROLE_HIERARCHY = ROLE_HIERARCHY;
|
|
40
|
+
exports.ValidationErrorCode = ValidationErrorCode;
|
|
41
|
+
exports.hasRolePermission = hasRolePermission;
|
|
42
|
+
//# sourceMappingURL=index.js.map
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types/index.ts"],"names":["PasswordErrorCode","EmailErrorCode","ValidationErrorCode"],"mappings":";;;AA8HO,IAAM,cAAA,GAA8C;AAAA,EACzD,KAAA,EAAO,CAAA;AAAA,EACP,KAAA,EAAO,CAAA;AAAA,EACP,MAAA,EAAQ,CAAA;AAAA,EACR,MAAA,EAAQ;AACV;AAKO,SAAS,iBAAA,CACd,UACA,YAAA,EACS;AACT,EAAA,OAAO,cAAA,CAAe,QAAQ,CAAA,IAAK,cAAA,CAAe,YAAY,CAAA;AAChE;AAyEO,IAAK,iBAAA,qBAAAA,kBAAAA,KAAL;AACL,EAAAA,mBAAA,WAAA,CAAA,GAAY,oBAAA;AACZ,EAAAA,mBAAA,YAAA,CAAA,GAAa,qBAAA;AACb,EAAAA,mBAAA,kBAAA,CAAA,GAAmB,2BAAA;AACnB,EAAAA,mBAAA,aAAA,CAAA,GAAc,sBAAA;AACd,EAAAA,mBAAA,UAAA,CAAA,GAAW,mBAAA;AACX,EAAAA,mBAAA,UAAA,CAAA,GAAW,mBAAA;AAND,EAAA,OAAAA,kBAAAA;AAAA,CAAA,EAAA,iBAAA,IAAA,EAAA;AAYL,IAAK,cAAA,qBAAAC,eAAAA,KAAL;AACL,EAAAA,gBAAA,gBAAA,CAAA,GAAiB,sBAAA;AACjB,EAAAA,gBAAA,UAAA,CAAA,GAAW,gBAAA;AACX,EAAAA,gBAAA,WAAA,CAAA,GAAY,iBAAA;AACZ,EAAAA,gBAAA,gBAAA,CAAA,GAAiB,sBAAA;AAJP,EAAA,OAAAA,eAAAA;AAAA,CAAA,EAAA,cAAA,IAAA,EAAA;AAUL,IAAK,mBAAA,qBAAAC,oBAAAA,KAAL;AACL,EAAAA,qBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,qBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,qBAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,qBAAA,UAAA,CAAA,GAAW,UAAA;AAJD,EAAA,OAAAA,oBAAAA;AAAA,CAAA,EAAA,mBAAA,IAAA,EAAA","file":"index.js","sourcesContent":["/**\n * Authentication types for StartSimpli apps\n */\n\n/**\n * Generic token pair (access + optional refresh)\n */\nexport interface TokenPair {\n access: string;\n refresh?: string;\n}\n\n/**\n * Generic decoded JWT payload — framework and backend agnostic\n */\nexport interface DecodedToken {\n sub?: string;\n email?: string;\n exp?: number;\n iat?: number;\n [key: string]: unknown;\n}\n\n/**\n * Generic auth session — framework agnostic\n */\nexport interface AuthSession {\n user: {\n id: string;\n email: string;\n [key: string]: unknown;\n };\n accessToken: string;\n refreshToken?: string;\n}\n\n/**\n * User profile from Django backend\n */\nexport interface AuthUser {\n id: string;\n email: string;\n firstName: string;\n lastName: string;\n isEmailVerified: boolean;\n createdAt: string;\n updatedAt: string;\n // Company/team context (if applicable)\n companies?: Array<{\n id: string;\n name: string;\n role: 'owner' | 'admin' | 'member' | 'viewer';\n }>;\n currentCompanyId?: string;\n}\n\n/**\n * JWT token payload structure\n */\nexport interface TokenPayload {\n token_type: 'access';\n exp: number;\n iat: number;\n jti: string;\n user_id: string;\n}\n\n/**\n * Session data stored in client\n */\nexport interface Session {\n user: AuthUser;\n accessToken: string;\n expiresAt: number;\n}\n\n/**\n * Login response from Django backend\n */\nexport interface LoginResponse {\n access: string;\n user: AuthUser;\n}\n\n/**\n * Token refresh response\n */\nexport interface RefreshResponse {\n access: string;\n}\n\n/**\n * Permission check result\n */\nexport interface PermissionCheck {\n hasPermission: boolean;\n reason?: string;\n}\n\n/**\n * Auth configuration options\n */\nexport interface AuthConfig {\n apiBaseUrl: string;\n tokenRefreshInterval?: number; // milliseconds, default 4 minutes\n onSessionExpired?: () => void;\n onUnauthorized?: () => void;\n}\n\n/**\n * Auth state for React context\n */\nexport interface AuthState {\n session: Session | null;\n isLoading: boolean;\n isAuthenticated: boolean;\n}\n\n/**\n * Company role hierarchy\n */\nexport type CompanyRole = 'owner' | 'admin' | 'member' | 'viewer';\n\n/**\n * Role hierarchy map (higher number = more permissions)\n */\nexport const ROLE_HIERARCHY: Record<CompanyRole, number> = {\n owner: 4,\n admin: 3,\n member: 2,\n viewer: 1,\n};\n\n/**\n * Check if role has sufficient permissions\n */\nexport function hasRolePermission(\n userRole: CompanyRole,\n requiredRole: CompanyRole\n): boolean {\n return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole];\n}\n\n/**\n * Password reset request payload\n * Initiates password reset flow by sending reset email\n */\nexport interface PasswordResetRequest {\n email: string;\n clientMetadata?: Record<string, any>;\n}\n\n/**\n * Password reset confirmation payload\n * Completes password reset with token and new password\n */\nexport interface PasswordResetConfirm {\n token: string;\n password: string;\n passwordConfirm: string;\n}\n\n/**\n * Email verification request payload\n * Verifies user email with token from verification email\n */\nexport interface EmailVerificationRequest {\n token: string;\n}\n\n/**\n * Email verification response\n * Returns updated user data after successful verification\n */\nexport interface EmailVerificationResponse {\n detail: string;\n user: {\n id: string;\n email: string;\n isEmailVerified: boolean;\n };\n}\n\n/**\n * API error response from Django backend\n * Standard error format for all API errors\n */\nexport interface ApiErrorResponse {\n detail?: string;\n message?: string;\n errors?: Record<string, string[]>;\n code?: string;\n status?: number;\n}\n\n/**\n * Validation error detail\n * Individual field validation error\n */\nexport interface ValidationError {\n field: string;\n message: string;\n code?: string;\n}\n\n/**\n * Validation errors map\n * Maps field names to error messages\n */\nexport type ValidationErrorsMap = Record<string, string[]>;\n\n/**\n * Password validation error codes\n */\nexport enum PasswordErrorCode {\n TOO_SHORT = 'password_too_short',\n TOO_COMMON = 'password_too_common',\n ENTIRELY_NUMERIC = 'password_entirely_numeric',\n TOO_SIMILAR = 'password_too_similar',\n MISMATCH = 'password_mismatch',\n REQUIRED = 'password_required',\n}\n\n/**\n * Email validation error codes\n */\nexport enum EmailErrorCode {\n INVALID_FORMAT = 'email_invalid_format',\n REQUIRED = 'email_required',\n NOT_FOUND = 'email_not_found',\n ALREADY_EXISTS = 'email_already_exists',\n}\n\n/**\n * General validation error codes\n */\nexport enum ValidationErrorCode {\n REQUIRED = 'required',\n INVALID = 'invalid',\n TOO_SHORT = 'too_short',\n TOO_LONG = 'too_long',\n}\n"]}
|