@smittdev/next-jwt-auth 0.1.0
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/files/lib/auth/client/index.ts +2 -0
- package/dist/files/lib/auth/client/provider.tsx +424 -0
- package/dist/files/lib/auth/config.ts +54 -0
- package/dist/files/lib/auth/core/config.ts +57 -0
- package/dist/files/lib/auth/core/cookies.ts +92 -0
- package/dist/files/lib/auth/core/index.ts +14 -0
- package/dist/files/lib/auth/core/jwt.ts +65 -0
- package/dist/files/lib/auth/index.ts +112 -0
- package/dist/files/lib/auth/middleware/auth-middleware.ts +191 -0
- package/dist/files/lib/auth/middleware/index.ts +3 -0
- package/dist/files/lib/auth/server/actions.ts +352 -0
- package/dist/files/lib/auth/server/fetchers.ts +40 -0
- package/dist/files/lib/auth/server/index.ts +12 -0
- package/dist/files/lib/auth/server/session.ts +158 -0
- package/dist/files/lib/auth/types.ts +227 -0
- package/dist/index.js +1128 -0
- package/package.json +41 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { fetchSessionAction, loginAction, logoutAction, updateSessionTokenAction } from "./server/actions";
|
|
3
|
+
|
|
4
|
+
// ─── Core Domain Types ────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The user object stored in the session.
|
|
8
|
+
* Extend this interface via module augmentation in your auth.ts to add
|
|
9
|
+
* custom fields (name, role, avatarUrl, etc.):
|
|
10
|
+
*
|
|
11
|
+
* declare module "@/lib/auth" {
|
|
12
|
+
* interface SessionUser {
|
|
13
|
+
* name: string;
|
|
14
|
+
* role: "admin" | "user";
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
export interface SessionUser {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The shape of a decoded JWT payload. We only require `exp`. */
|
|
24
|
+
export interface TokenPayload {
|
|
25
|
+
exp: number;
|
|
26
|
+
iat?: number;
|
|
27
|
+
sub?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** An access + refresh token pair returned by login or refresh operations. */
|
|
32
|
+
export interface TokenPair {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
refreshToken: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A fully resolved server-side session. */
|
|
38
|
+
export interface Session {
|
|
39
|
+
accessToken: string;
|
|
40
|
+
refreshToken: string;
|
|
41
|
+
user: SessionUser;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Adapter ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The adapter you implement in auth.ts.
|
|
48
|
+
* Three functions are required; logout is optional.
|
|
49
|
+
*/
|
|
50
|
+
export interface AuthAdapter {
|
|
51
|
+
/** Authenticate with credentials, return a token pair or throw. */
|
|
52
|
+
login(credentials: Record<string, unknown>): Promise<TokenPair>;
|
|
53
|
+
/** Exchange a refresh token for a new token pair or throw. */
|
|
54
|
+
refreshToken(refreshToken: string): Promise<TokenPair>;
|
|
55
|
+
/** Fetch the user object for a given access token or throw. */
|
|
56
|
+
fetchUser(accessToken: string): Promise<SessionUser>;
|
|
57
|
+
/** Optional: invalidate the refresh token server-side on logout. */
|
|
58
|
+
logout?(tokens: TokenPair): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export interface CookieOptions {
|
|
64
|
+
/** Base name for cookies. Defaults to "auth-session". */
|
|
65
|
+
name?: string;
|
|
66
|
+
/** Defaults to true in production, false in development. */
|
|
67
|
+
secure?: boolean;
|
|
68
|
+
sameSite?: "strict" | "lax" | "none";
|
|
69
|
+
domain?: string;
|
|
70
|
+
path?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RefreshOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Access tokens with less than this many seconds remaining will be
|
|
76
|
+
* silently refreshed. Defaults to 120 (2 minutes).
|
|
77
|
+
*/
|
|
78
|
+
refreshThresholdSeconds?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AuthPages {
|
|
82
|
+
/** The sign-in page path. Defaults to "/login". Also used as the post-logout redirect. */
|
|
83
|
+
signIn?: string;
|
|
84
|
+
/** Where to redirect after successful login. Defaults to "/". */
|
|
85
|
+
home?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AuthConfig {
|
|
89
|
+
adapter: AuthAdapter;
|
|
90
|
+
cookies?: CookieOptions;
|
|
91
|
+
refresh?: RefreshOptions;
|
|
92
|
+
pages?: AuthPages;
|
|
93
|
+
/**
|
|
94
|
+
* Enable verbose debug logging to the console.
|
|
95
|
+
* Logs token refresh decisions, session resolution, middleware activity,
|
|
96
|
+
* and action outcomes. Should only be enabled in development.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* debug: process.env.NODE_ENV === "development",
|
|
100
|
+
*/
|
|
101
|
+
debug?: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Resolved Config (internal) ──────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export interface ResolvedCookieNames {
|
|
107
|
+
accessToken: string;
|
|
108
|
+
refreshToken: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface ResolvedAuthConfig {
|
|
112
|
+
adapter: AuthAdapter;
|
|
113
|
+
cookieNames: ResolvedCookieNames;
|
|
114
|
+
cookieOptions: Required<Omit<CookieOptions, "name" | "domain">> & {
|
|
115
|
+
domain?: string;
|
|
116
|
+
};
|
|
117
|
+
refreshThresholdSeconds: number;
|
|
118
|
+
pages: Required<AuthPages>;
|
|
119
|
+
/** Whether debug logging is enabled. */
|
|
120
|
+
debug: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Client Session ───────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
export type SessionStatus = "loading" | "authenticated" | "unauthenticated";
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The discriminated union returned by useSession().
|
|
129
|
+
* Check session.status before accessing session.user / session.accessToken.
|
|
130
|
+
*
|
|
131
|
+
* - "loading" — initial state when no `initialSession` prop was passed.
|
|
132
|
+
* The provider is fetching the session from the server on mount.
|
|
133
|
+
* - "authenticated" — a valid session exists.
|
|
134
|
+
* - "unauthenticated" — no session. Either `initialSession={null}` was passed
|
|
135
|
+
* explicitly, or the mount fetch returned no session.
|
|
136
|
+
*
|
|
137
|
+
* To avoid the loading state entirely, pass `initialSession` from your root
|
|
138
|
+
* layout Server Component — the client will hydrate instantly with no fetch.
|
|
139
|
+
*/
|
|
140
|
+
export type ClientSession =
|
|
141
|
+
| {
|
|
142
|
+
status: "loading";
|
|
143
|
+
user: null;
|
|
144
|
+
accessToken: null;
|
|
145
|
+
refreshToken: null;
|
|
146
|
+
}
|
|
147
|
+
| {
|
|
148
|
+
status: "authenticated";
|
|
149
|
+
user: SessionUser;
|
|
150
|
+
accessToken: string;
|
|
151
|
+
refreshToken: string;
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
status: "unauthenticated";
|
|
155
|
+
user: null;
|
|
156
|
+
accessToken: null;
|
|
157
|
+
refreshToken: null;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// ─── Action Results ───────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/** The standard result shape for all Server Actions in this library. */
|
|
163
|
+
export type ActionResult<TData> =
|
|
164
|
+
| { success: true; data: TData }
|
|
165
|
+
| { success: false; error: string };
|
|
166
|
+
|
|
167
|
+
export type SessionActionData = Session;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Options accepted by the login Server Action.
|
|
171
|
+
* All fields are optional — login works with no options, defaulting to a
|
|
172
|
+
* redirect to `pages.home`.
|
|
173
|
+
*/
|
|
174
|
+
export interface LoginActionOptions {
|
|
175
|
+
/**
|
|
176
|
+
* Whether to redirect after a successful login. Defaults to true.
|
|
177
|
+
* Set to false to handle navigation yourself on the client.
|
|
178
|
+
*/
|
|
179
|
+
redirect?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Explicit redirect destination after login. Takes priority over callbackUrl
|
|
182
|
+
* and pages.home.
|
|
183
|
+
*/
|
|
184
|
+
redirectTo?: string;
|
|
185
|
+
/**
|
|
186
|
+
* A relative path to redirect to after login — typically read from the
|
|
187
|
+
* `?callbackUrl=` search param set by requireSession().
|
|
188
|
+
* Must start with "/" to prevent open-redirect attacks; invalid values
|
|
189
|
+
* are silently ignored and fall back to pages.home.
|
|
190
|
+
*/
|
|
191
|
+
callbackUrl?: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The server actions object passed to <AuthProvider actions={...}>. */
|
|
195
|
+
export type AuthActions = {
|
|
196
|
+
login: typeof loginAction;
|
|
197
|
+
logout: typeof logoutAction;
|
|
198
|
+
fetchSession: typeof fetchSessionAction;
|
|
199
|
+
updateSessionToken: typeof updateSessionTokenAction;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// ─── Zod Schemas (runtime validation) ────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
export const TokenPairSchema = z.object({
|
|
205
|
+
accessToken: z
|
|
206
|
+
.string()
|
|
207
|
+
.min(1, "accessToken must be a non-empty string")
|
|
208
|
+
.refine(
|
|
209
|
+
(token) => token.split(".").length === 3,
|
|
210
|
+
"accessToken does not appear to be a valid JWT (expected 3 dot-separated segments)",
|
|
211
|
+
),
|
|
212
|
+
refreshToken: z
|
|
213
|
+
.string()
|
|
214
|
+
.min(1, "refreshToken must be a non-empty string")
|
|
215
|
+
.refine(
|
|
216
|
+
(token) => token.split(".").length === 3,
|
|
217
|
+
"refreshToken does not appear to be a valid JWT (expected 3 dot-separated segments)",
|
|
218
|
+
),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export const TokenPayloadSchema = z.object({
|
|
222
|
+
exp: z.number({
|
|
223
|
+
error: "JWT payload is missing the required `exp` claim",
|
|
224
|
+
}),
|
|
225
|
+
iat: z.number().optional(),
|
|
226
|
+
sub: z.string().optional(),
|
|
227
|
+
});
|