@rimelight/auth 0.0.8 → 0.0.9

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.
@@ -111,5 +111,12 @@ export declare function mapAuth0PayloadToSession(payload: Record<string, any>, o
111
111
  permissionsClaim: string;
112
112
  defaultRole: string;
113
113
  }): UserSessionContext;
114
+ export declare function getAuth0ConfigFromEnv(overrides?: Partial<Auth0AdapterOptions>): Auth0AdapterOptions;
115
+ /**
116
+ * Creates an Auth0 auth adapter automatically configured from environment variables.
117
+ */
118
+ export declare function createAuth0FromEnv(overrides?: Partial<Auth0AdapterOptions> & {
119
+ devFallback?: boolean;
120
+ }): AuthAdapter;
114
121
  //#endregion
115
122
  export { decodeJwt };
@@ -252,5 +252,60 @@ function mapAuth0PayloadToSession(payload, options) {
252
252
  metadata: payload
253
253
  };
254
254
  }
255
+ function getEnv(key, fallback = "") {
256
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
257
+ if (typeof import.meta !== "undefined" && import.meta.env?.[key]) return import.meta.env[key];
258
+ return fallback;
259
+ }
260
+ function getAuth0ConfigFromEnv(overrides = {}) {
261
+ return {
262
+ domain: overrides.domain || getEnv("AUTH0_DOMAIN", "rimelight.eu.auth0.com"),
263
+ audience: overrides.audience || (getEnv("AUTH0_USE_CUSTOM_AUDIENCE") === "true" ? getEnv("AUTH0_AUDIENCE") || void 0 : void 0),
264
+ clientId: overrides.clientId || getEnv("AUTH0_CLIENT_ID", ""),
265
+ clientSecret: overrides.clientSecret || getEnv("AUTH0_CLIENT_SECRET", ""),
266
+ rolesClaim: overrides.rolesClaim || getEnv("AUTH0_ROLES_CLAIM", "https://rimelight.com/roles"),
267
+ userTypeClaim: overrides.userTypeClaim || getEnv("AUTH0_USER_TYPE_CLAIM", "https://rimelight.com/user_type"),
268
+ permissionsClaim: overrides.permissionsClaim || "permissions",
269
+ sessionCookieName: overrides.sessionCookieName || "rimelight_session",
270
+ sessionSecret: overrides.sessionSecret || getEnv("AUTH0_SECRET", "rimelight_default_development_secret_key_32bytes_minimum_length"),
271
+ loginUrl: overrides.loginUrl || "/api/auth/login",
272
+ defaultRole: overrides.defaultRole || "user",
273
+ ...overrides
274
+ };
275
+ }
276
+ /**
277
+ * Creates an Auth0 auth adapter automatically configured from environment variables.
278
+ */
279
+ function createAuth0FromEnv(overrides = {}) {
280
+ const config = getAuth0ConfigFromEnv(overrides);
281
+ const baseAdapter = auth0Auth(config);
282
+ if (!overrides.devFallback) return baseAdapter;
283
+ return {
284
+ name: "auth0",
285
+ options: config,
286
+ async getSession(req) {
287
+ const session = await baseAdapter.getSession(req);
288
+ if (session) return session;
289
+ if ((typeof process !== "undefined" && process.env?.["NODE_ENV"] === "development" || typeof import.meta !== "undefined" && import.meta.env?.DEV) && (!config.clientId || config.clientId === "" || config.clientId === "default_client_id")) return {
290
+ userId: "dev-admin-user",
291
+ email: "admin@rimelight.com",
292
+ name: "Admin (Dev)",
293
+ avatar: "https://cdn.rimelight.com/Images/default_avatar.png",
294
+ roles: [
295
+ "admin",
296
+ "owner",
297
+ "editor"
298
+ ],
299
+ permissions: ["*"],
300
+ userType: "employee",
301
+ metadata: { isDev: true }
302
+ };
303
+ return null;
304
+ },
305
+ handleUnauthorized(req) {
306
+ return baseAdapter.handleUnauthorized?.(req) ?? new Response("Unauthorized", { status: 401 });
307
+ }
308
+ };
309
+ }
255
310
  //#endregion
256
- export { auth0Auth, createAuth0AuthorizeUrl, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken };
311
+ export { auth0Auth, createAuth0AuthorizeUrl, createAuth0FromEnv, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, getAuth0ConfigFromEnv, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken };
@@ -9,4 +9,8 @@ export interface CfAccessAdapterOptions {
9
9
  loginUrl?: string | undefined;
10
10
  }
11
11
  export declare function cfAccessAuth(options?: CfAccessAdapterOptions): AuthAdapter;
12
+ /**
13
+ * Creates a Cloudflare Access auth adapter automatically configured from environment variables.
14
+ */
15
+ export declare function createCfAccessFromEnv(overrides?: Partial<CfAccessAdapterOptions>): AuthAdapter;
12
16
  //#endregion
@@ -62,5 +62,23 @@ function cfAccessAuth(options = {}) {
62
62
  }
63
63
  };
64
64
  }
65
+ function getEnv(key, fallback = "") {
66
+ if (typeof process !== "undefined" && process.env?.[key]) return process.env[key];
67
+ if (typeof import.meta !== "undefined" && import.meta.env?.[key]) return import.meta.env[key];
68
+ return fallback;
69
+ }
70
+ /**
71
+ * Creates a Cloudflare Access auth adapter automatically configured from environment variables.
72
+ */
73
+ function createCfAccessFromEnv(overrides = {}) {
74
+ return cfAccessAuth({
75
+ teamDomain: overrides.teamDomain || getEnv("CF_ACCESS_TEAM_DOMAIN"),
76
+ aud: overrides.aud || getEnv("CF_ACCESS_AUD"),
77
+ verifyJwt: overrides.verifyJwt ?? (getEnv("CF_ACCESS_VERIFY_JWT") === "true" || typeof import.meta !== "undefined" && import.meta.env?.["CF_ACCESS_VERIFY_JWT"] === "true"),
78
+ adminEmails: overrides.adminEmails || (getEnv("CF_ACCESS_ADMIN_EMAILS") ? getEnv("CF_ACCESS_ADMIN_EMAILS").split(",").map((e) => e.trim()) : []),
79
+ defaultRole: overrides.defaultRole || "admin",
80
+ ...overrides
81
+ });
82
+ }
65
83
  //#endregion
66
- export { cfAccessAuth };
84
+ export { cfAccessAuth, createCfAccessFromEnv };
@@ -0,0 +1,32 @@
1
+ import { UserSessionContext } from "../types.mjs";
2
+ //#region src/client/index.d.ts
3
+ export interface AuthClientSession<TUser = UserSessionContext> {
4
+ user: TUser | null;
5
+ session: {
6
+ id: string;
7
+ userId: string;
8
+ roles?: string[];
9
+ userType?: string;
10
+ } | null;
11
+ }
12
+ export interface AuthClientOptions {
13
+ sessionEndpoint?: string;
14
+ meEndpoint?: string;
15
+ loginEndpoint?: string;
16
+ logoutEndpoint?: string;
17
+ fetch?: typeof fetch;
18
+ }
19
+ export interface AuthClient<TUser = UserSessionContext> {
20
+ getSession(): Promise<AuthClientSession<TUser>>;
21
+ getMe(): Promise<TUser | null>;
22
+ login(returnTo?: string, extraParams?: Record<string, string>): void;
23
+ signup(returnTo?: string, extraParams?: Record<string, string>): void;
24
+ signOut(returnTo?: string): void;
25
+ }
26
+ /**
27
+ * Universal browser-side Auth Client factory.
28
+ */
29
+ export declare function createAuthClient<TUser = UserSessionContext>(options?: AuthClientOptions): AuthClient<TUser>;
30
+ export declare const authClient: AuthClient<UserSessionContext>;
31
+ //#endregion
32
+ export { authClient as default };
@@ -0,0 +1,62 @@
1
+ //#region src/client/index.ts
2
+ /**
3
+ * Universal browser-side Auth Client factory.
4
+ */
5
+ function createAuthClient(options = {}) {
6
+ const { sessionEndpoint = "/api/auth/session", meEndpoint = "/api/auth/me", loginEndpoint = "/api/auth/login", logoutEndpoint = "/api/auth/logout", fetch: customFetch = typeof fetch !== "undefined" ? fetch : void 0 } = options;
7
+ return {
8
+ async getSession() {
9
+ try {
10
+ if (!customFetch) return {
11
+ user: null,
12
+ session: null
13
+ };
14
+ const res = await customFetch(sessionEndpoint);
15
+ if (!res.ok) return {
16
+ user: null,
17
+ session: null
18
+ };
19
+ return await res.json();
20
+ } catch {
21
+ return {
22
+ user: null,
23
+ session: null
24
+ };
25
+ }
26
+ },
27
+ async getMe() {
28
+ try {
29
+ if (!customFetch) return null;
30
+ const res = await customFetch(meEndpoint);
31
+ if (!res.ok) return null;
32
+ return (await res.json())?.user ?? null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ },
37
+ login(returnTo = "/", extraParams = {}) {
38
+ if (typeof window === "undefined") return;
39
+ const url = new URL(loginEndpoint, window.location.origin);
40
+ url.searchParams.set("returnTo", returnTo);
41
+ for (const [k, v] of Object.entries(extraParams)) url.searchParams.set(k, v);
42
+ window.location.href = url.toString();
43
+ },
44
+ signup(returnTo = "/", extraParams = {}) {
45
+ if (typeof window === "undefined") return;
46
+ const url = new URL(loginEndpoint, window.location.origin);
47
+ url.searchParams.set("screen_hint", "signup");
48
+ url.searchParams.set("returnTo", returnTo);
49
+ for (const [k, v] of Object.entries(extraParams)) url.searchParams.set(k, v);
50
+ window.location.href = url.toString();
51
+ },
52
+ signOut(returnTo = "/") {
53
+ if (typeof window === "undefined") return;
54
+ const url = new URL(logoutEndpoint, window.location.origin);
55
+ url.searchParams.set("returnTo", returnTo);
56
+ window.location.href = url.toString();
57
+ }
58
+ };
59
+ }
60
+ const authClient = createAuthClient();
61
+ //#endregion
62
+ export { authClient, authClient as default, createAuthClient };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,15 @@
1
- import { AuthAdapter, UserSessionContext, UserType } from "./types.mjs";
2
- import { Auth0AdapterOptions, Auth0AuthorizeUrlOptions, Auth0CodeExchangeOptions, Auth0PasswordLoginOptions, Auth0RefreshTokenOptions, Auth0TokenResponse, auth0Auth, createAuth0AuthorizeUrl, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken } from "./adapters/auth0.mjs";
3
- import { CfAccessAdapterOptions, cfAccessAuth } from "./adapters/cf-access.mjs";
1
+ import { AuthAdapter, AuthMiddlewareOptions, RoleGuard, UserSessionContext, UserType } from "./types.mjs";
2
+ import { Auth0AdapterOptions, Auth0AuthorizeUrlOptions, Auth0CodeExchangeOptions, Auth0PasswordLoginOptions, Auth0RefreshTokenOptions, Auth0TokenResponse, auth0Auth, createAuth0AuthorizeUrl, createAuth0FromEnv, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, getAuth0ConfigFromEnv, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken } from "./adapters/auth0.mjs";
3
+ import { CfAccessAdapterOptions, cfAccessAuth, createCfAccessFromEnv } from "./adapters/cf-access.mjs";
4
4
  import { MockAuthOptions, mockAuth } from "./adapters/mock.mjs";
5
+ import { AuthClient, AuthClientOptions, AuthClientSession, authClient, createAuthClient } from "./client/index.mjs";
5
6
  import { AccessControl, AccessRole, StatementMap, adminAc, createAccessControl, createPermissions, defaultStatements, evaluateAccess, hasPermission, hasRole, memberAc, ownerAc } from "./permissions/index.mjs";
6
7
  import { RESTRICTED_SET, STANDARD_RESTRICTED_GROUPS, createRestrictedUsernameSet, normalizeUsername } from "./plugins/reserved-usernames/index.mjs";
7
8
  import { CONSTRUCTION_GUEST_COOKIE, GuestEnv, isConstructionGuest, signInConstructionGuest } from "./plugins/construction-guest/index.mjs";
8
- export { AccessControl, AccessRole, Auth0AdapterOptions, Auth0AuthorizeUrlOptions, Auth0CodeExchangeOptions, Auth0PasswordLoginOptions, Auth0RefreshTokenOptions, Auth0TokenResponse, AuthAdapter, CONSTRUCTION_GUEST_COOKIE, CfAccessAdapterOptions, GuestEnv, MockAuthOptions, RESTRICTED_SET, STANDARD_RESTRICTED_GROUPS, StatementMap, UserSessionContext, UserType, adminAc, auth0Auth, cfAccessAuth, createAccessControl, createAuth0AuthorizeUrl, createAuth0LogoutUrl, createPermissions, createRestrictedUsernameSet, createSessionToken, decodeJwt, defaultStatements, evaluateAccess, exchangeAuth0Code, hasPermission, hasRole, isConstructionGuest, loginWithAuth0Password, mapAuth0PayloadToSession, memberAc, mockAuth, normalizeUsername, ownerAc, refreshAuth0Token, signInConstructionGuest, verifyAuth0Jwt, verifySessionToken };
9
+ import { authMiddleware } from "./middleware/index.mjs";
10
+ import { RimelightAuthPlugin, RimelightAuthPluginOptions } from "./vite.mjs";
11
+ //#region src/index.d.ts
12
+ export declare const auth: AuthAdapter;
13
+ export declare const authAdapter: AuthAdapter;
14
+ //#endregion
15
+ export { AccessControl, AccessRole, Auth0AdapterOptions, Auth0AuthorizeUrlOptions, Auth0CodeExchangeOptions, Auth0PasswordLoginOptions, Auth0RefreshTokenOptions, Auth0TokenResponse, AuthAdapter, AuthClient, AuthClientOptions, AuthClientSession, AuthMiddlewareOptions, CONSTRUCTION_GUEST_COOKIE, CfAccessAdapterOptions, GuestEnv, MockAuthOptions, RESTRICTED_SET, RimelightAuthPlugin, RimelightAuthPluginOptions, RoleGuard, STANDARD_RESTRICTED_GROUPS, StatementMap, UserSessionContext, UserType, adminAc, auth as default, auth0Auth, authClient, authMiddleware, cfAccessAuth, createAccessControl, createAuth0AuthorizeUrl, createAuth0FromEnv, createAuth0LogoutUrl, createAuthClient, createCfAccessFromEnv, createPermissions, createRestrictedUsernameSet, createSessionToken, decodeJwt, defaultStatements, evaluateAccess, exchangeAuth0Code, getAuth0ConfigFromEnv, hasPermission, hasRole, isConstructionGuest, loginWithAuth0Password, mapAuth0PayloadToSession, memberAc, mockAuth, normalizeUsername, ownerAc, refreshAuth0Token, signInConstructionGuest, verifyAuth0Jwt, verifySessionToken };
package/dist/index.mjs CHANGED
@@ -1,8 +1,28 @@
1
- import { auth0Auth, createAuth0AuthorizeUrl, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken } from "./adapters/auth0.mjs";
2
- import { cfAccessAuth } from "./adapters/cf-access.mjs";
1
+ import { auth0Auth, createAuth0AuthorizeUrl, createAuth0FromEnv, createAuth0LogoutUrl, createSessionToken, decodeJwt, exchangeAuth0Code, getAuth0ConfigFromEnv, loginWithAuth0Password, mapAuth0PayloadToSession, refreshAuth0Token, verifyAuth0Jwt, verifySessionToken } from "./adapters/auth0.mjs";
2
+ import { cfAccessAuth, createCfAccessFromEnv } from "./adapters/cf-access.mjs";
3
3
  import { mockAuth } from "./adapters/mock.mjs";
4
+ import { authClient, createAuthClient } from "./client/index.mjs";
4
5
  import "./types.mjs";
5
6
  import { AccessControl, adminAc, createAccessControl, createPermissions, defaultStatements, evaluateAccess, hasPermission, hasRole, memberAc, ownerAc } from "./permissions/index.mjs";
6
7
  import { RESTRICTED_SET, STANDARD_RESTRICTED_GROUPS, createRestrictedUsernameSet, normalizeUsername } from "./plugins/reserved-usernames/index.mjs";
7
8
  import { CONSTRUCTION_GUEST_COOKIE, isConstructionGuest, signInConstructionGuest } from "./plugins/construction-guest/index.mjs";
8
- export { AccessControl, CONSTRUCTION_GUEST_COOKIE, RESTRICTED_SET, STANDARD_RESTRICTED_GROUPS, adminAc, auth0Auth, cfAccessAuth, createAccessControl, createAuth0AuthorizeUrl, createAuth0LogoutUrl, createPermissions, createRestrictedUsernameSet, createSessionToken, decodeJwt, defaultStatements, evaluateAccess, exchangeAuth0Code, hasPermission, hasRole, isConstructionGuest, loginWithAuth0Password, mapAuth0PayloadToSession, memberAc, mockAuth, normalizeUsername, ownerAc, refreshAuth0Token, signInConstructionGuest, verifyAuth0Jwt, verifySessionToken };
9
+ import { authMiddleware } from "./middleware/index.mjs";
10
+ import "./vite.mjs";
11
+ //#region src/index.ts
12
+ let _defaultAuth = null;
13
+ function getDefaultAuth() {
14
+ if (!_defaultAuth) _defaultAuth = typeof process !== "undefined" && Boolean(process.env?.["AUTH0_DOMAIN"]) || typeof import.meta !== "undefined" && Boolean(import.meta.env?.["AUTH0_DOMAIN"]) ? createAuth0FromEnv({ devFallback: true }) : createCfAccessFromEnv();
15
+ return _defaultAuth;
16
+ }
17
+ const auth = {
18
+ name: "auto",
19
+ async getSession(req) {
20
+ return getDefaultAuth().getSession(req);
21
+ },
22
+ handleUnauthorized(req) {
23
+ return getDefaultAuth().handleUnauthorized?.(req) ?? new Response("Unauthorized", { status: 401 });
24
+ }
25
+ };
26
+ const authAdapter = auth;
27
+ //#endregion
28
+ export { AccessControl, CONSTRUCTION_GUEST_COOKIE, RESTRICTED_SET, STANDARD_RESTRICTED_GROUPS, adminAc, auth, auth as default, auth0Auth, authAdapter, authClient, authMiddleware, cfAccessAuth, createAccessControl, createAuth0AuthorizeUrl, createAuth0FromEnv, createAuth0LogoutUrl, createAuthClient, createCfAccessFromEnv, createPermissions, createRestrictedUsernameSet, createSessionToken, decodeJwt, defaultStatements, evaluateAccess, exchangeAuth0Code, getAuth0ConfigFromEnv, hasPermission, hasRole, isConstructionGuest, loginWithAuth0Password, mapAuth0PayloadToSession, memberAc, mockAuth, normalizeUsername, ownerAc, refreshAuth0Token, signInConstructionGuest, verifyAuth0Jwt, verifySessionToken };
@@ -0,0 +1,20 @@
1
+ import { AuthMiddlewareOptions } from "../types.mjs";
2
+ //#region src/middleware/index.d.ts
3
+ /**
4
+ * Universal Authentication & Authorization middleware for Hono applications.
5
+ *
6
+ * @example
7
+ * import { authMiddleware } from "@rimelight/auth/middleware"
8
+ * import { auth } from "#auth/auth"
9
+ *
10
+ * app.use(
11
+ * authMiddleware({
12
+ * auth,
13
+ * roleGuards: {
14
+ * "/admin": ["admin", "owner"]
15
+ * }
16
+ * })
17
+ * )
18
+ */
19
+ export declare const authMiddleware: (options?: AuthMiddlewareOptions) => (c: any, next: () => Promise<any> | any) => Promise<Response | any>;
20
+ //#endregion
@@ -0,0 +1,174 @@
1
+ import { isConstructionGuest } from "../plugins/construction-guest/index.mjs";
2
+ //#region src/middleware/index.ts
3
+ const STATIC_ASSET_REGEX = /\.(css|js|map|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico|json|webmanifest)$/i;
4
+ const DEFAULT_IGNORED_ROUTES = ["/docs/"];
5
+ const DEFAULT_PROTECTED_ROUTES = ["/internal"];
6
+ const DEFAULT_GUEST_ONLY_ROUTES = ["/auth/sign-in", "/auth/sign-up"];
7
+ function matchesPattern(path, pattern) {
8
+ if (typeof pattern === "function") return pattern(path);
9
+ if (typeof pattern === "string") return path.startsWith(pattern) || path.includes(pattern);
10
+ return pattern.test(path);
11
+ }
12
+ /**
13
+ * Universal Authentication & Authorization middleware for Hono applications.
14
+ *
15
+ * @example
16
+ * import { authMiddleware } from "@rimelight/auth/middleware"
17
+ * import { auth } from "#auth/auth"
18
+ *
19
+ * app.use(
20
+ * authMiddleware({
21
+ * auth,
22
+ * roleGuards: {
23
+ * "/admin": ["admin", "owner"]
24
+ * }
25
+ * })
26
+ * )
27
+ */
28
+ const authMiddleware = (options = {}) => {
29
+ const { auth, allowConstructionGuest = true, skipStaticAssets = true, ignoredRoutes = DEFAULT_IGNORED_ROUTES, protectedRoutes = DEFAULT_PROTECTED_ROUTES, roleGuards = {}, guestOnlyRoutes = DEFAULT_GUEST_ONLY_ROUTES, authRedirectUrl = "/auth/sign-in", signInUrl = "/auth/sign-in", homeUrl = "/", onUnauthorized, onForbidden } = options;
30
+ return async (c, next) => {
31
+ const rawUrl = c?.req?.url || "http://localhost/";
32
+ const pathname = new URL(rawUrl).pathname;
33
+ const isApi = pathname.startsWith("/api/");
34
+ if (skipStaticAssets) {
35
+ if (pathname.startsWith("/_astro/") || pathname.startsWith("/assets/") || pathname === "/favicon.ico" || pathname.startsWith("/open-graph/") || STATIC_ASSET_REGEX.test(pathname)) return await next();
36
+ }
37
+ if (typeof ignoredRoutes === "function" ? ignoredRoutes(pathname) : ignoredRoutes.some((pattern) => matchesPattern(pathname, pattern))) {
38
+ if (typeof c?.set === "function") {
39
+ c.set("user", null);
40
+ c.set("session", null);
41
+ }
42
+ } else {
43
+ let sessionCtx = null;
44
+ if (auth) try {
45
+ if (typeof auth === "function") sessionCtx = await auth(c);
46
+ else if (typeof auth.getSession === "function") {
47
+ const req = c.req?.raw || c.req;
48
+ sessionCtx = await auth.getSession(req);
49
+ }
50
+ } catch (err) {
51
+ console.error("[authMiddleware] Failed to resolve session:", err);
52
+ }
53
+ let isGuest = false;
54
+ if (!sessionCtx && allowConstructionGuest) try {
55
+ isGuest = await isConstructionGuest(c);
56
+ } catch {
57
+ isGuest = false;
58
+ }
59
+ const user = sessionCtx ?? (isGuest ? {
60
+ userId: "construction-guest",
61
+ email: "guest",
62
+ name: "Guest",
63
+ roles: ["guest"],
64
+ permissions: [],
65
+ userType: "user",
66
+ metadata: {}
67
+ } : null);
68
+ const session = sessionCtx ? {
69
+ id: sessionCtx.userId,
70
+ userId: sessionCtx.userId,
71
+ roles: sessionCtx.roles,
72
+ userType: sessionCtx.userType
73
+ } : isGuest ? {
74
+ id: "construction-guest",
75
+ userId: "construction-guest",
76
+ roles: ["guest"],
77
+ userType: "user"
78
+ } : null;
79
+ if (typeof c?.set === "function") {
80
+ c.set("user", user);
81
+ c.set("session", session);
82
+ }
83
+ }
84
+ const session = typeof c?.get === "function" ? c.get("session") : null;
85
+ const user = typeof c?.get === "function" ? c.get("user") : null;
86
+ if (pathname === "/auth") return c.redirect(authRedirectUrl);
87
+ if (guestOnlyRoutes.some((route) => matchesPattern(pathname, route)) && session) return c.redirect(homeUrl);
88
+ if (pathname.includes("/construction") && session) return c.redirect(homeUrl);
89
+ if ((typeof protectedRoutes === "function" ? protectedRoutes(pathname) : protectedRoutes.some((pattern) => matchesPattern(pathname, pattern))) && !session) {
90
+ if (onUnauthorized) return onUnauthorized(c, {
91
+ isApi,
92
+ path: pathname
93
+ });
94
+ if (isApi) {
95
+ if (typeof c.json === "function") return c.json({
96
+ error: "Unauthorized",
97
+ message: "Authentication required."
98
+ }, 401);
99
+ return new Response(JSON.stringify({
100
+ error: "Unauthorized",
101
+ message: "Authentication required."
102
+ }), {
103
+ status: 401,
104
+ headers: { "Content-Type": "application/json" }
105
+ });
106
+ }
107
+ return c.redirect(signInUrl);
108
+ }
109
+ for (const [guardPath, guardConfig] of Object.entries(roleGuards)) if (matchesPattern(pathname, guardPath)) {
110
+ if (!session) {
111
+ if (onUnauthorized) return onUnauthorized(c, {
112
+ isApi,
113
+ path: pathname
114
+ });
115
+ if (isApi) {
116
+ if (typeof c.json === "function") return c.json({
117
+ error: "Unauthorized",
118
+ message: "Authentication required."
119
+ }, 401);
120
+ return new Response(JSON.stringify({
121
+ error: "Unauthorized",
122
+ message: "Authentication required."
123
+ }), {
124
+ status: 401,
125
+ headers: { "Content-Type": "application/json" }
126
+ });
127
+ }
128
+ return c.redirect(signInUrl);
129
+ }
130
+ let isAllowed = false;
131
+ const userRoles = (user?.roles || []).map((r) => r.toLowerCase());
132
+ const userType = user?.userType;
133
+ if (Array.isArray(guardConfig)) {
134
+ const allowedRoles = guardConfig.map((r) => r.toLowerCase());
135
+ isAllowed = userRoles.includes("*") || allowedRoles.some((r) => userRoles.includes(r));
136
+ } else if (typeof guardConfig === "object" && guardConfig !== null) {
137
+ const { roles, userTypes, check } = guardConfig;
138
+ if (check) isAllowed = await check(user, c);
139
+ else {
140
+ const roleMatch = roles ? userRoles.includes("*") || roles.some((r) => userRoles.includes(r.toLowerCase())) : true;
141
+ const typeMatch = userTypes ? userType ? userTypes.includes(userType) : false : true;
142
+ isAllowed = roleMatch && typeMatch;
143
+ }
144
+ }
145
+ if (!isAllowed) {
146
+ if (onForbidden) {
147
+ const requiredRoles = Array.isArray(guardConfig) ? guardConfig : guardConfig.roles;
148
+ return onForbidden(c, {
149
+ isApi,
150
+ path: pathname,
151
+ requiredRoles
152
+ });
153
+ }
154
+ if (isApi) {
155
+ if (typeof c.json === "function") return c.json({
156
+ error: "Forbidden",
157
+ message: "Forbidden: insufficient permissions."
158
+ }, 403);
159
+ return new Response(JSON.stringify({
160
+ error: "Forbidden",
161
+ message: "Forbidden: insufficient permissions."
162
+ }), {
163
+ status: 403,
164
+ headers: { "Content-Type": "application/json" }
165
+ });
166
+ }
167
+ return c.redirect(homeUrl);
168
+ }
169
+ }
170
+ return await next();
171
+ };
172
+ };
173
+ //#endregion
174
+ export { authMiddleware };
package/dist/types.d.mts CHANGED
@@ -54,4 +54,86 @@ export interface AuthAdapter {
54
54
  */
55
55
  handleUnauthorized?: ((request: Request) => Response | Promise<Response>) | undefined;
56
56
  }
57
+ export interface RoleGuard {
58
+ roles?: string[];
59
+ userTypes?: UserType[];
60
+ check?: (user: UserSessionContext | null, c: any) => boolean | Promise<boolean>;
61
+ }
62
+ export interface AuthMiddlewareOptions {
63
+ /**
64
+ * The auth adapter instance or custom session resolver function.
65
+ */
66
+ auth?: AuthAdapter | {
67
+ getSession(req: Request): Promise<UserSessionContext | null>;
68
+ } | ((c: any) => Promise<UserSessionContext | null>);
69
+ /**
70
+ * Fall back to construction guest session if no user is signed in.
71
+ *
72
+ * @default true
73
+ */
74
+ allowConstructionGuest?: boolean;
75
+ /**
76
+ * Whether to automatically skip static asset paths.
77
+ *
78
+ * @default true
79
+ */
80
+ skipStaticAssets?: boolean;
81
+ /**
82
+ * Route patterns to completely bypass session resolution.
83
+ *
84
+ * @default ["/docs/"]
85
+ */
86
+ ignoredRoutes?: (string | RegExp)[] | ((path: string) => boolean);
87
+ /**
88
+ * Route patterns that require an authenticated session.
89
+ *
90
+ * @default ["/internal"]
91
+ */
92
+ protectedRoutes?: (string | RegExp)[] | ((path: string) => boolean);
93
+ /**
94
+ * Role / permission requirements for specific route prefixes. E.g.: { "/admin": ["admin",
95
+ * "owner"], "/internal": { roles: ["employee", "staff", "admin", "owner"], userTypes:
96
+ * ["employee"] } }
97
+ */
98
+ roleGuards?: Record<string, string[] | RoleGuard>;
99
+ /**
100
+ * Routes that should redirect to home/dashboard if user is already logged in.
101
+ *
102
+ * @default ["/auth/sign-in", "/auth/sign-up"]
103
+ */
104
+ guestOnlyRoutes?: (string | RegExp)[];
105
+ /**
106
+ * URL to redirect to when `/auth` is accessed directly.
107
+ *
108
+ * @default "/auth/sign-in"
109
+ */
110
+ authRedirectUrl?: string;
111
+ /**
112
+ * URL to redirect unauthenticated users accessing protected pages.
113
+ *
114
+ * @default "/auth/sign-in"
115
+ */
116
+ signInUrl?: string;
117
+ /**
118
+ * Home URL to redirect logged-in users away from guest routes or forbidden pages.
119
+ *
120
+ * @default "/"
121
+ */
122
+ homeUrl?: string;
123
+ /**
124
+ * Custom response when unauthorized (not logged in).
125
+ */
126
+ onUnauthorized?: (c: any, meta: {
127
+ isApi: boolean;
128
+ path: string;
129
+ }) => Response | Promise<Response>;
130
+ /**
131
+ * Custom response when forbidden (insufficient role/permissions).
132
+ */
133
+ onForbidden?: (c: any, meta: {
134
+ isApi: boolean;
135
+ path: string;
136
+ requiredRoles?: string[];
137
+ }) => Response | Promise<Response>;
138
+ }
57
139
  //#endregion
@@ -0,0 +1,33 @@
1
+ //#region src/vite.d.ts
2
+ export interface RimelightAuthPluginOptions {
3
+ /**
4
+ * Auth adapter to initialize: "cf-access" (default), "auth0", "mock", or "custom".
5
+ *
6
+ * @default "cf-access"
7
+ */
8
+ adapter?: "cf-access" | "auth0" | "mock" | "custom" | (string & {});
9
+ /**
10
+ * Path to custom auth module if adapter is set to "custom".
11
+ */
12
+ customModule?: string;
13
+ /**
14
+ * Additional adapter-specific configuration overrides.
15
+ */
16
+ [key: string]: any;
17
+ }
18
+ export interface RimelightAuthPlugin {
19
+ name: string;
20
+ resolveId: (id: string) => string | null | undefined;
21
+ load: (id: string) => string | null | undefined;
22
+ }
23
+ /**
24
+ * Pure Vite Auth Plugin for Rimelight applications.
25
+ *
26
+ * Exposes virtual modules:
27
+ *
28
+ * - `virtual:rimelight-auth` (alias `#auth/auth`, `#auth`)
29
+ * - `virtual:rimelight-auth-client` (alias `#auth/auth-client`)
30
+ * - `virtual:rimelight-auth-config`
31
+ */
32
+ export declare function auth(options?: RimelightAuthPluginOptions): RimelightAuthPlugin;
33
+ //#endregion
package/dist/vite.mjs ADDED
@@ -0,0 +1,66 @@
1
+ //#region src/vite.ts
2
+ /**
3
+ * Pure Vite Auth Plugin for Rimelight applications.
4
+ *
5
+ * Exposes virtual modules:
6
+ *
7
+ * - `virtual:rimelight-auth` (alias `#auth/auth`, `#auth`)
8
+ * - `virtual:rimelight-auth-client` (alias `#auth/auth-client`)
9
+ * - `virtual:rimelight-auth-config`
10
+ */
11
+ function auth(options = {}) {
12
+ const adapter = options.adapter || "cf-access";
13
+ const virtualAuthId = "virtual:rimelight-auth";
14
+ const resolvedVirtualAuthId = "\0" + virtualAuthId;
15
+ const virtualClientId = "virtual:rimelight-auth-client";
16
+ const resolvedVirtualClientId = "\0" + virtualClientId;
17
+ const virtualConfigId = "virtual:rimelight-auth-config";
18
+ const resolvedVirtualConfigId = "\0" + virtualConfigId;
19
+ return {
20
+ name: "vite-plugin-rimelight-auth",
21
+ resolveId(id) {
22
+ if (id === virtualAuthId || id === "#auth" || id === "#auth/auth") return resolvedVirtualAuthId;
23
+ if (id === virtualClientId || id === "#auth/auth-client") return resolvedVirtualClientId;
24
+ if (id === virtualConfigId) return resolvedVirtualConfigId;
25
+ return null;
26
+ },
27
+ load(id) {
28
+ if (id === resolvedVirtualConfigId) return `export const config = ${JSON.stringify(options)};`;
29
+ if (id === resolvedVirtualClientId) return `
30
+ import { createAuthClient, authClient } from "@rimelight/auth/client";
31
+ export { createAuthClient, authClient };
32
+ export default authClient;
33
+ `;
34
+ if (id === resolvedVirtualAuthId) {
35
+ if (adapter === "custom" && options.customModule) return `
36
+ export * from "${options.customModule}";
37
+ export { default } from "${options.customModule}";
38
+ `;
39
+ if (adapter === "auth0") return `
40
+ import { createAuth0FromEnv } from "@rimelight/auth/auth0";
41
+ export const auth = createAuth0FromEnv(${JSON.stringify({
42
+ devFallback: true,
43
+ ...options
44
+ })});
45
+ export const authAdapter = auth;
46
+ export default auth;
47
+ `;
48
+ if (adapter === "mock") return `
49
+ import { mockAuth } from "@rimelight/auth/mock";
50
+ export const auth = mockAuth(${JSON.stringify(options)});
51
+ export const authAdapter = auth;
52
+ export default auth;
53
+ `;
54
+ return `
55
+ import { createCfAccessFromEnv } from "@rimelight/auth/cf-access";
56
+ export const auth = createCfAccessFromEnv(${JSON.stringify(options)});
57
+ export const authAdapter = auth;
58
+ export default auth;
59
+ `;
60
+ }
61
+ return null;
62
+ }
63
+ };
64
+ }
65
+ //#endregion
66
+ export { auth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/auth",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's Universal Authentication & Identity Package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -51,6 +51,18 @@
51
51
  "./plugins/construction-guest": {
52
52
  "types": "./dist/plugins/construction-guest/index.d.mts",
53
53
  "import": "./dist/plugins/construction-guest/index.mjs"
54
+ },
55
+ "./middleware": {
56
+ "types": "./dist/middleware/index.d.mts",
57
+ "import": "./dist/middleware/index.mjs"
58
+ },
59
+ "./client": {
60
+ "types": "./dist/client/index.d.mts",
61
+ "import": "./dist/client/index.mjs"
62
+ },
63
+ "./vite": {
64
+ "types": "./dist/vite.d.mts",
65
+ "import": "./dist/vite.mjs"
54
66
  }
55
67
  },
56
68
  "publishConfig": {
@@ -61,7 +73,7 @@
61
73
  "jose": "6.2.12"
62
74
  },
63
75
  "devDependencies": {
64
- "@rimelight/config": "0.0.12",
76
+ "@rimelight/config": "0.0.13",
65
77
  "@types/node": "26.5.1"
66
78
  },
67
79
  "peerDependencies": {
@@ -77,7 +89,7 @@
77
89
  }
78
90
  },
79
91
  "engines": {
80
- "node": ">=26.9.0"
92
+ "node": ">=26.8.2"
81
93
  },
82
94
  "scripts": {
83
95
  "build": "vp pack",