nuxt-bearer-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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +265 -0
  3. package/dist/module.cjs +5 -0
  4. package/dist/module.d.mts +134 -0
  5. package/dist/module.d.ts +134 -0
  6. package/dist/module.json +12 -0
  7. package/dist/module.mjs +222 -0
  8. package/dist/runtime/composables/useBearerAuth.d.ts +34 -0
  9. package/dist/runtime/composables/useBearerAuth.js +259 -0
  10. package/dist/runtime/middleware/bearer-auth.global.d.ts +3 -0
  11. package/dist/runtime/middleware/bearer-auth.global.js +28 -0
  12. package/dist/runtime/plugins/bearer-auth.server.d.ts +3 -0
  13. package/dist/runtime/plugins/bearer-auth.server.js +26 -0
  14. package/dist/runtime/server/api/auth/forgot-password.post.d.ts +3 -0
  15. package/dist/runtime/server/api/auth/forgot-password.post.js +16 -0
  16. package/dist/runtime/server/api/auth/login.post.d.ts +15 -0
  17. package/dist/runtime/server/api/auth/login.post.js +54 -0
  18. package/dist/runtime/server/api/auth/logout.post.d.ts +6 -0
  19. package/dist/runtime/server/api/auth/logout.post.js +20 -0
  20. package/dist/runtime/server/api/auth/me.get.d.ts +5 -0
  21. package/dist/runtime/server/api/auth/me.get.js +37 -0
  22. package/dist/runtime/server/api/auth/otp-verification.post.d.ts +8 -0
  23. package/dist/runtime/server/api/auth/otp-verification.post.js +38 -0
  24. package/dist/runtime/server/api/auth/refresh.post.d.ts +7 -0
  25. package/dist/runtime/server/api/auth/refresh.post.js +37 -0
  26. package/dist/runtime/server/api/auth/register.post.d.ts +8 -0
  27. package/dist/runtime/server/api/auth/register.post.js +32 -0
  28. package/dist/runtime/server/api/auth/resend-otp/[identifier].post.d.ts +3 -0
  29. package/dist/runtime/server/api/auth/resend-otp/[identifier].post.js +21 -0
  30. package/dist/runtime/server/api/auth/reset-password.post.d.ts +3 -0
  31. package/dist/runtime/server/api/auth/reset-password.post.js +16 -0
  32. package/dist/runtime/server/api/auth/sessions/[id].delete.d.ts +6 -0
  33. package/dist/runtime/server/api/auth/sessions/[id].delete.js +20 -0
  34. package/dist/runtime/server/api/auth/sessions.get.d.ts +5 -0
  35. package/dist/runtime/server/api/auth/sessions.get.js +11 -0
  36. package/dist/runtime/server/api/auth/social-login.post.d.ts +7 -0
  37. package/dist/runtime/server/api/auth/social-login.post.js +41 -0
  38. package/dist/runtime/server/middleware/auth.d.ts +3 -0
  39. package/dist/runtime/server/middleware/auth.js +29 -0
  40. package/dist/runtime/server/plugins/redis.d.ts +3 -0
  41. package/dist/runtime/server/plugins/redis.js +23 -0
  42. package/dist/runtime/server/utils/config.d.ts +16 -0
  43. package/dist/runtime/server/utils/config.js +24 -0
  44. package/dist/runtime/server/utils/errors.d.ts +2 -0
  45. package/dist/runtime/server/utils/errors.js +20 -0
  46. package/dist/runtime/server/utils/external-api.d.ts +15 -0
  47. package/dist/runtime/server/utils/external-api.js +31 -0
  48. package/dist/runtime/server/utils/normalize.d.ts +16 -0
  49. package/dist/runtime/server/utils/normalize.js +30 -0
  50. package/dist/runtime/server/utils/paths.d.ts +4 -0
  51. package/dist/runtime/server/utils/paths.js +23 -0
  52. package/dist/runtime/server/utils/sessions.d.ts +4645 -0
  53. package/dist/runtime/server/utils/sessions.js +207 -0
  54. package/dist/runtime/types/auth.d.ts +49 -0
  55. package/dist/runtime/types/auth.js +0 -0
  56. package/dist/runtime/types/h3.d.ts +7 -0
  57. package/dist/types.d.mts +7 -0
  58. package/dist/types.d.ts +7 -0
  59. package/package.json +58 -0
@@ -0,0 +1,207 @@
1
+ import crypto from "node:crypto";
2
+ import { createClient } from "redis";
3
+ import {
4
+ createError,
5
+ deleteCookie,
6
+ getCookie,
7
+ getHeaders,
8
+ getRequestIP,
9
+ setCookie
10
+ } from "h3";
11
+ import {
12
+ getBearerAuthConfig,
13
+ getSessionCookieName,
14
+ isProductionRuntime
15
+ } from "./config.js";
16
+ const SESSION_PREFIX = "session:";
17
+ const USER_SESSIONS_PREFIX = "user_sessions:";
18
+ const globalForRedis = globalThis;
19
+ export function getBearerAuthRedisClient() {
20
+ if (!globalForRedis.__nuxtBearerAuthRedis) {
21
+ const config = getBearerAuthConfig();
22
+ const client = createClient({
23
+ url: config.redisUrl || process.env.REDIS_URL || "redis://127.0.0.1:6379",
24
+ socket: {
25
+ reconnectStrategy: (retries) => Math.min(retries * 100, 3e3),
26
+ connectTimeout: 1e4
27
+ }
28
+ });
29
+ client.on("error", (error) => {
30
+ console.error("[nuxt-bearer-auth] Redis error:", error);
31
+ });
32
+ globalForRedis.__nuxtBearerAuthRedis = client;
33
+ }
34
+ return globalForRedis.__nuxtBearerAuthRedis;
35
+ }
36
+ export async function ensureBearerAuthRedisConnection() {
37
+ const redis = getBearerAuthRedisClient();
38
+ if (!redis.isOpen && !redis.isReady) {
39
+ await redis.connect();
40
+ }
41
+ return redis;
42
+ }
43
+ function getSessionDuration() {
44
+ return getBearerAuthConfig().sessionCookie.maxAge;
45
+ }
46
+ function getCookieOptions() {
47
+ const config = getBearerAuthConfig();
48
+ const cookie = config.sessionCookie;
49
+ return {
50
+ httpOnly: true,
51
+ sameSite: cookie.sameSite,
52
+ secure: cookie.secure ?? isProductionRuntime(),
53
+ domain: cookie.domain,
54
+ path: cookie.path || "/",
55
+ maxAge: cookie.maxAge
56
+ };
57
+ }
58
+ export async function createBearerAuthSession(event, input) {
59
+ const redis = await ensureBearerAuthRedisConnection();
60
+ const sessionId = crypto.randomUUID();
61
+ const headers = getHeaders(event);
62
+ const now = (/* @__PURE__ */ new Date()).toISOString();
63
+ const duration = getSessionDuration();
64
+ const session = {
65
+ userId: input.userId,
66
+ token: input.token,
67
+ refreshToken: input.refreshToken || null,
68
+ profile: input.profile || null,
69
+ createdAt: now,
70
+ expiresAt: Date.now() + duration * 1e3,
71
+ lastActivity: now,
72
+ userAgent: headers["user-agent"],
73
+ ipAddress: getRequestIP(event, { xForwardedFor: true })
74
+ };
75
+ const multi = redis.multi();
76
+ multi.setEx(`${SESSION_PREFIX}${sessionId}`, duration, JSON.stringify(session));
77
+ multi.sAdd(`${USER_SESSIONS_PREFIX}${input.userId}`, sessionId);
78
+ multi.expire(`${USER_SESSIONS_PREFIX}${input.userId}`, duration);
79
+ await multi.exec();
80
+ setCookie(event, getSessionCookieName(), sessionId, getCookieOptions());
81
+ return sessionId;
82
+ }
83
+ export async function updateBearerAuthSession(event, updates) {
84
+ const sessionId = getBearerAuthSessionCookie(event);
85
+ if (!sessionId) return false;
86
+ const redis = await ensureBearerAuthRedisConnection();
87
+ const key = `${SESSION_PREFIX}${sessionId}`;
88
+ const data = await redis.get(key);
89
+ if (!data) return false;
90
+ const existing = JSON.parse(data);
91
+ if (Date.now() > existing.expiresAt) {
92
+ await destroyBearerAuthSession(event);
93
+ return false;
94
+ }
95
+ const duration = getSessionDuration();
96
+ const session = {
97
+ ...existing,
98
+ ...updates,
99
+ expiresAt: Date.now() + duration * 1e3,
100
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
101
+ };
102
+ await redis.setEx(key, duration, JSON.stringify(session));
103
+ event.context.auth = session;
104
+ return true;
105
+ }
106
+ export async function getBearerAuthSession(event) {
107
+ const sessionId = getBearerAuthSessionCookie(event);
108
+ if (!sessionId) return null;
109
+ const redis = await ensureBearerAuthRedisConnection();
110
+ const key = `${SESSION_PREFIX}${sessionId}`;
111
+ const data = await redis.get(key);
112
+ if (!data) {
113
+ deleteCookie(event, getSessionCookieName(), { path: "/" });
114
+ return null;
115
+ }
116
+ const session = JSON.parse(data);
117
+ if (Date.now() > session.expiresAt) {
118
+ await destroyBearerAuthSession(event);
119
+ return null;
120
+ }
121
+ updateSessionActivity(sessionId, session).catch(() => void 0);
122
+ return session;
123
+ }
124
+ async function updateSessionActivity(sessionId, session) {
125
+ const redis = await ensureBearerAuthRedisConnection();
126
+ const duration = getSessionDuration();
127
+ const updatedSession = {
128
+ ...session,
129
+ expiresAt: Date.now() + duration * 1e3,
130
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
131
+ };
132
+ await redis.setEx(
133
+ `${SESSION_PREFIX}${sessionId}`,
134
+ duration,
135
+ JSON.stringify(updatedSession)
136
+ );
137
+ }
138
+ export async function destroyBearerAuthSession(event) {
139
+ const sessionId = getBearerAuthSessionCookie(event);
140
+ if (!sessionId) {
141
+ deleteCookie(event, getSessionCookieName(), { path: "/" });
142
+ return;
143
+ }
144
+ const redis = await ensureBearerAuthRedisConnection();
145
+ const key = `${SESSION_PREFIX}${sessionId}`;
146
+ const data = await redis.get(key);
147
+ if (data) {
148
+ const session = JSON.parse(data);
149
+ await redis.sRem(`${USER_SESSIONS_PREFIX}${session.userId}`, sessionId);
150
+ }
151
+ await redis.del(key);
152
+ deleteCookie(event, getSessionCookieName(), { path: "/" });
153
+ }
154
+ export async function destroyAllBearerAuthSessions(userId) {
155
+ const redis = await ensureBearerAuthRedisConnection();
156
+ const key = `${USER_SESSIONS_PREFIX}${userId}`;
157
+ const sessionIds = await redis.sMembers(key);
158
+ if (!sessionIds.length) return;
159
+ const multi = redis.multi();
160
+ for (const sessionId of sessionIds) {
161
+ multi.del(`${SESSION_PREFIX}${sessionId}`);
162
+ }
163
+ multi.del(key);
164
+ await multi.exec();
165
+ }
166
+ export async function getUserBearerAuthSessions(userId) {
167
+ const redis = await ensureBearerAuthRedisConnection();
168
+ const sessionIds = await redis.sMembers(`${USER_SESSIONS_PREFIX}${userId}`);
169
+ const sessions = [];
170
+ for (const sessionId of sessionIds) {
171
+ const data = await redis.get(`${SESSION_PREFIX}${sessionId}`);
172
+ if (!data) continue;
173
+ const session = JSON.parse(data);
174
+ sessions.push({
175
+ id: sessionId,
176
+ createdAt: session.createdAt,
177
+ lastActivity: session.lastActivity,
178
+ userAgent: session.userAgent,
179
+ ipAddress: session.ipAddress
180
+ });
181
+ }
182
+ return sessions.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
183
+ }
184
+ export async function deleteUserBearerAuthSession(userId, sessionId) {
185
+ const redis = await ensureBearerAuthRedisConnection();
186
+ const key = `${SESSION_PREFIX}${sessionId}`;
187
+ const data = await redis.get(key);
188
+ if (!data) {
189
+ throw createError({ statusCode: 404, statusMessage: "Session not found" });
190
+ }
191
+ const session = JSON.parse(data);
192
+ if (session.userId !== userId) {
193
+ throw createError({ statusCode: 403, statusMessage: "Unauthorized" });
194
+ }
195
+ await redis.del(key);
196
+ await redis.sRem(`${USER_SESSIONS_PREFIX}${userId}`, sessionId);
197
+ }
198
+ export function getBearerAuthSessionCookie(event) {
199
+ return getCookie(event, getSessionCookieName()) || null;
200
+ }
201
+ export function requireBearerAuthSession(event) {
202
+ const session = event.context.auth;
203
+ if (!session?.userId) {
204
+ throw createError({ statusCode: 401, statusMessage: "Unauthenticated" });
205
+ }
206
+ return session;
207
+ }
@@ -0,0 +1,49 @@
1
+ export type AuthStatus = "idle" | "loading" | "authenticated" | "unauthenticated";
2
+ export interface BearerAuthUser {
3
+ id?: string | number;
4
+ uuid?: string;
5
+ email?: string;
6
+ name?: string;
7
+ [key: string]: unknown;
8
+ }
9
+ export interface BearerAuthSession<User extends BearerAuthUser = BearerAuthUser> {
10
+ userId: string;
11
+ token: string;
12
+ refreshToken?: string | null;
13
+ profile: User | null;
14
+ createdAt: string;
15
+ expiresAt: number;
16
+ lastActivity: string;
17
+ userAgent?: string;
18
+ ipAddress?: string;
19
+ }
20
+ export interface PublicSession {
21
+ id: string;
22
+ createdAt: string;
23
+ lastActivity: string;
24
+ userAgent?: string;
25
+ ipAddress?: string;
26
+ }
27
+ export interface AuthApiResponse<T = unknown> {
28
+ success: boolean;
29
+ message?: string;
30
+ data?: T;
31
+ user?: BearerAuthUser | null;
32
+ code?: string | number;
33
+ nextAction?: string;
34
+ }
35
+ export interface LoginCredentials {
36
+ identifier: string;
37
+ password: string;
38
+ [key: string]: unknown;
39
+ }
40
+ export interface SocialLoginCredentials {
41
+ jwt?: string;
42
+ token?: string;
43
+ provider?: string;
44
+ [key: string]: unknown;
45
+ }
46
+ export interface FetchUserOptions {
47
+ refresh?: boolean;
48
+ }
49
+ //# sourceMappingURL=auth.d.ts.map
File without changes
@@ -0,0 +1,7 @@
1
+ import type { BearerAuthSession } from "./auth";
2
+
3
+ declare module "h3" {
4
+ interface H3EventContext {
5
+ auth?: BearerAuthSession | null;
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module.js'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module.js'
@@ -0,0 +1,7 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { default } from './module'
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "nuxt-bearer-auth",
3
+ "version": "0.1.0",
4
+ "description": "Reusable Nuxt authentication module for bearer-token APIs with Redis sessions and CSRF protection.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Enoch Tetteh",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": ""
11
+ },
12
+ "keywords": [
13
+ "nuxt",
14
+ "nuxt-module",
15
+ "auth",
16
+ "authentication",
17
+ "bearer-token",
18
+ "redis",
19
+ "csrf",
20
+ "laravel"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/module.d.ts",
25
+ "import": "./dist/module.mjs"
26
+ }
27
+ },
28
+ "main": "./dist/module.mjs",
29
+ "types": "./dist/module.d.ts",
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "nuxt-module-build build",
36
+ "dev:prepare": "nuxt-module-build --stub",
37
+ "prepack": "nuxt-module-build build",
38
+ "typecheck": "vue-tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "defu": "^6.1.4",
42
+ "ofetch": "^1.4.1",
43
+ "redis": "^5.11.0"
44
+ },
45
+ "peerDependencies": {
46
+ "nuxt": "^3.12.0 || ^4.0.0",
47
+ "nuxt-csurf": "^1.6.5"
48
+ },
49
+ "devDependencies": {
50
+ "@nuxt/module-builder": "^0.8.4",
51
+ "@nuxt/schema": "^3.12.0",
52
+ "@nuxt/kit": "^3.12.0",
53
+ "nuxt": "^3.12.0",
54
+ "typescript": "^5.5.0",
55
+ "vue-tsc": "^2.0.0"
56
+ },
57
+ "packageManager": "npm@10.0.0"
58
+ }