@fnd-platform/cognito-auth 1.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +323 -0
- package/lib/authorizer/handler.d.ts +33 -0
- package/lib/authorizer/handler.d.ts.map +1 -0
- package/lib/authorizer/handler.js +106 -0
- package/lib/authorizer/handler.js.map +1 -0
- package/lib/authorizer/index.d.ts +7 -0
- package/lib/authorizer/index.d.ts.map +1 -0
- package/lib/authorizer/index.js +16 -0
- package/lib/authorizer/index.js.map +1 -0
- package/lib/client/auth-client.d.ts +131 -0
- package/lib/client/auth-client.d.ts.map +1 -0
- package/lib/client/auth-client.js +270 -0
- package/lib/client/auth-client.js.map +1 -0
- package/lib/client/errors.d.ts +67 -0
- package/lib/client/errors.d.ts.map +1 -0
- package/lib/client/errors.js +90 -0
- package/lib/client/errors.js.map +1 -0
- package/lib/client/index.d.ts +8 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +29 -0
- package/lib/client/index.js.map +1 -0
- package/lib/cognito-construct.d.ts +113 -0
- package/lib/cognito-construct.d.ts.map +1 -0
- package/lib/cognito-construct.js +211 -0
- package/lib/cognito-construct.js.map +1 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +59 -0
- package/lib/index.js.map +1 -0
- package/lib/jwt.d.ts +89 -0
- package/lib/jwt.d.ts.map +1 -0
- package/lib/jwt.js +117 -0
- package/lib/jwt.js.map +1 -0
- package/lib/middleware/auth.d.ts +59 -0
- package/lib/middleware/auth.d.ts.map +1 -0
- package/lib/middleware/auth.js +148 -0
- package/lib/middleware/auth.js.map +1 -0
- package/lib/middleware/index.d.ts +12 -0
- package/lib/middleware/index.d.ts.map +1 -0
- package/lib/middleware/index.js +16 -0
- package/lib/middleware/index.js.map +1 -0
- package/lib/remix/admin.server.d.ts +105 -0
- package/lib/remix/admin.server.d.ts.map +1 -0
- package/lib/remix/admin.server.js +146 -0
- package/lib/remix/admin.server.js.map +1 -0
- package/lib/remix/index.d.ts +17 -0
- package/lib/remix/index.d.ts.map +1 -0
- package/lib/remix/index.js +95 -0
- package/lib/remix/index.js.map +1 -0
- package/lib/remix/session.server.d.ts +177 -0
- package/lib/remix/session.server.d.ts.map +1 -0
- package/lib/remix/session.server.js +287 -0
- package/lib/remix/session.server.js.map +1 -0
- package/lib/types.d.ts +161 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +8 -0
- package/lib/types.js.map +1 -0
- package/lib/utils/index.d.ts +12 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/index.js +22 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/token-refresh.d.ts +62 -0
- package/lib/utils/token-refresh.d.ts.map +1 -0
- package/lib/utils/token-refresh.js +84 -0
- package/lib/utils/token-refresh.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cognito authentication middleware with real JWT validation.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.withCognitoAuth = withCognitoAuth;
|
|
9
|
+
const jwt_js_1 = require("../jwt.js");
|
|
10
|
+
/**
|
|
11
|
+
* Creates an unauthorized response.
|
|
12
|
+
*
|
|
13
|
+
* @param message - Error message
|
|
14
|
+
* @returns API Gateway response with 401 status
|
|
15
|
+
*/
|
|
16
|
+
function unauthorized(message) {
|
|
17
|
+
return {
|
|
18
|
+
statusCode: 401,
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Access-Control-Allow-Origin': '*',
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
success: false,
|
|
25
|
+
error: {
|
|
26
|
+
code: 'UNAUTHORIZED',
|
|
27
|
+
message,
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a forbidden response.
|
|
34
|
+
*
|
|
35
|
+
* @param message - Error message
|
|
36
|
+
* @returns API Gateway response with 403 status
|
|
37
|
+
*/
|
|
38
|
+
function forbidden(message) {
|
|
39
|
+
return {
|
|
40
|
+
statusCode: 403,
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Access-Control-Allow-Origin': '*',
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
success: false,
|
|
47
|
+
error: {
|
|
48
|
+
code: 'FORBIDDEN',
|
|
49
|
+
message,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extracts bearer token from Authorization header.
|
|
56
|
+
*
|
|
57
|
+
* @param event - API Gateway event
|
|
58
|
+
* @returns Token string or null if not found
|
|
59
|
+
*/
|
|
60
|
+
function extractBearerToken(event) {
|
|
61
|
+
const authHeader = event.headers?.Authorization ?? event.headers?.authorization;
|
|
62
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return authHeader.slice(7);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Middleware that validates JWT tokens from Cognito.
|
|
69
|
+
*
|
|
70
|
+
* Unlike the API package's withAuth (which expects API Gateway to validate),
|
|
71
|
+
* this middleware performs actual JWT verification using aws-jwt-verify.
|
|
72
|
+
*
|
|
73
|
+
* @param options - Authentication configuration
|
|
74
|
+
* @returns Middleware that validates tokens and adds auth info to event
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Basic auth - validates token
|
|
79
|
+
* const handler = withCognitoAuth()(async (event) => {
|
|
80
|
+
* const userId = event.auth.userId;
|
|
81
|
+
* return { statusCode: 200, body: JSON.stringify({ userId }) };
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // With role requirement
|
|
85
|
+
* const adminHandler = withCognitoAuth({ roles: ['admin'] })(async (event) => {
|
|
86
|
+
* return { statusCode: 200, body: 'Admin access granted' };
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Skip auth for certain paths
|
|
90
|
+
* const handler = withCognitoAuth({ skipPaths: ['/health'] })(async (event) => {
|
|
91
|
+
* return { statusCode: 200, body: 'OK' };
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
function withCognitoAuth(options = {}) {
|
|
96
|
+
const { userPoolId = process.env.COGNITO_USER_POOL_ID, clientId = process.env.COGNITO_CLIENT_ID, roles, skipPaths = [], tokenUse = 'access', } = options;
|
|
97
|
+
return (handler) => async (event, context) => {
|
|
98
|
+
// Skip auth for specified paths
|
|
99
|
+
if (skipPaths.some((path) => event.path?.includes(path))) {
|
|
100
|
+
// Pass through without auth info for skipped paths
|
|
101
|
+
return handler(event, context);
|
|
102
|
+
}
|
|
103
|
+
// Validate configuration
|
|
104
|
+
if (!userPoolId || !clientId) {
|
|
105
|
+
console.error('Missing COGNITO_USER_POOL_ID or COGNITO_CLIENT_ID');
|
|
106
|
+
return unauthorized('Authentication not configured');
|
|
107
|
+
}
|
|
108
|
+
// Extract token
|
|
109
|
+
const token = extractBearerToken(event);
|
|
110
|
+
if (!token) {
|
|
111
|
+
return unauthorized('Missing or invalid Authorization header');
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
// Verify token
|
|
115
|
+
const authResult = await (0, jwt_js_1.verifyAndExtract)(token, {
|
|
116
|
+
userPoolId,
|
|
117
|
+
clientId,
|
|
118
|
+
tokenUse,
|
|
119
|
+
});
|
|
120
|
+
// Check roles if specified
|
|
121
|
+
if (roles && roles.length > 0) {
|
|
122
|
+
const hasRole = roles.some((role) => authResult.groups.includes(role));
|
|
123
|
+
if (!hasRole) {
|
|
124
|
+
return forbidden(`Required role: ${roles.join(' or ')}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Add auth info to event
|
|
128
|
+
const authenticatedEvent = {
|
|
129
|
+
...event,
|
|
130
|
+
auth: authResult,
|
|
131
|
+
};
|
|
132
|
+
return handler(authenticatedEvent, context);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error('Token verification failed:', error);
|
|
136
|
+
// Provide specific error messages for common cases
|
|
137
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
138
|
+
if (errorMessage.includes('expired')) {
|
|
139
|
+
return unauthorized('Token has expired');
|
|
140
|
+
}
|
|
141
|
+
if (errorMessage.includes('invalid')) {
|
|
142
|
+
return unauthorized('Invalid token');
|
|
143
|
+
}
|
|
144
|
+
return unauthorized('Token verification failed');
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,yBAAyB,EAC9B,KAAK,UAAU,EACf,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware exports.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
8
|
+
exports.withCognitoAuth = void 0;
|
|
9
|
+
var auth_js_1 = require('./auth.js');
|
|
10
|
+
Object.defineProperty(exports, 'withCognitoAuth', {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: function () {
|
|
13
|
+
return auth_js_1.withCognitoAuth;
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,qCAKmB;AAJjB,0GAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin and role-based access utilities for Remix.
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for requiring specific roles in Remix loaders and actions.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import type { SessionStorage } from '@remix-run/node';
|
|
9
|
+
/**
|
|
10
|
+
* Requires admin role for accessing a route.
|
|
11
|
+
*
|
|
12
|
+
* First checks if the user is authenticated, then verifies they have
|
|
13
|
+
* the 'admin' group in their Cognito session.
|
|
14
|
+
*
|
|
15
|
+
* @param request - Remix request object
|
|
16
|
+
* @param storage - Optional custom session storage
|
|
17
|
+
* @returns User ID if authorized
|
|
18
|
+
* @throws Redirect to /login if not authenticated
|
|
19
|
+
* @throws 403 JSON response if not admin
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
24
|
+
* const userId = await requireAdmin(request);
|
|
25
|
+
* // User is admin, load admin data
|
|
26
|
+
* return json({ adminData: await getAdminData() });
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function requireAdmin(request: Request, storage?: SessionStorage): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Requires any of the specified roles for accessing a route.
|
|
33
|
+
*
|
|
34
|
+
* First checks if the user is authenticated, then verifies they have
|
|
35
|
+
* at least one of the required roles.
|
|
36
|
+
*
|
|
37
|
+
* @param request - Remix request object
|
|
38
|
+
* @param roles - Array of required roles (user must have at least one)
|
|
39
|
+
* @param storage - Optional custom session storage
|
|
40
|
+
* @returns User ID if authorized
|
|
41
|
+
* @throws Redirect to /login if not authenticated
|
|
42
|
+
* @throws 403 JSON response if missing required role
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
47
|
+
* // Require admin or editor role
|
|
48
|
+
* const userId = await requireRole(request, ['admin', 'editor']);
|
|
49
|
+
* return json({ content: await getContent() });
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function requireRole(
|
|
54
|
+
request: Request,
|
|
55
|
+
roles: string[],
|
|
56
|
+
storage?: SessionStorage
|
|
57
|
+
): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Checks if the user has a specific role.
|
|
60
|
+
*
|
|
61
|
+
* Does not throw errors or redirect. Returns false for unauthenticated users.
|
|
62
|
+
*
|
|
63
|
+
* @param request - Remix request object
|
|
64
|
+
* @param role - Role to check
|
|
65
|
+
* @param storage - Optional custom session storage
|
|
66
|
+
* @returns True if user has role, false otherwise
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
71
|
+
* const isAdmin = await hasRole(request, 'admin');
|
|
72
|
+
* const canEdit = await hasRole(request, 'editor');
|
|
73
|
+
* return json({ isAdmin, canEdit });
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function hasRole(
|
|
78
|
+
request: Request,
|
|
79
|
+
role: string,
|
|
80
|
+
storage?: SessionStorage
|
|
81
|
+
): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Checks if the user has any of the specified roles.
|
|
84
|
+
*
|
|
85
|
+
* Does not throw errors or redirect. Returns false for unauthenticated users.
|
|
86
|
+
*
|
|
87
|
+
* @param request - Remix request object
|
|
88
|
+
* @param roles - Roles to check
|
|
89
|
+
* @param storage - Optional custom session storage
|
|
90
|
+
* @returns True if user has any role, false otherwise
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
95
|
+
* const canManageContent = await hasAnyRole(request, ['admin', 'editor']);
|
|
96
|
+
* return json({ canManageContent });
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare function hasAnyRole(
|
|
101
|
+
request: Request,
|
|
102
|
+
roles: string[],
|
|
103
|
+
storage?: SessionStorage
|
|
104
|
+
): Promise<boolean>;
|
|
105
|
+
//# sourceMappingURL=admin.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.server.d.ts","sourceRoot":"","sources":["../../src/remix/admin.server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAE9F;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAclB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,OAAO,CAAC,CAclB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Admin and role-based access utilities for Remix.
|
|
4
|
+
*
|
|
5
|
+
* Provides utilities for requiring specific roles in Remix loaders and actions.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
10
|
+
exports.requireAdmin = requireAdmin;
|
|
11
|
+
exports.requireRole = requireRole;
|
|
12
|
+
exports.hasRole = hasRole;
|
|
13
|
+
exports.hasAnyRole = hasAnyRole;
|
|
14
|
+
const node_1 = require('@remix-run/node');
|
|
15
|
+
const session_server_js_1 = require('./session.server.js');
|
|
16
|
+
/**
|
|
17
|
+
* Requires admin role for accessing a route.
|
|
18
|
+
*
|
|
19
|
+
* First checks if the user is authenticated, then verifies they have
|
|
20
|
+
* the 'admin' group in their Cognito session.
|
|
21
|
+
*
|
|
22
|
+
* @param request - Remix request object
|
|
23
|
+
* @param storage - Optional custom session storage
|
|
24
|
+
* @returns User ID if authorized
|
|
25
|
+
* @throws Redirect to /login if not authenticated
|
|
26
|
+
* @throws 403 JSON response if not admin
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
31
|
+
* const userId = await requireAdmin(request);
|
|
32
|
+
* // User is admin, load admin data
|
|
33
|
+
* return json({ adminData: await getAdminData() });
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
async function requireAdmin(request, storage) {
|
|
38
|
+
return requireRole(request, ['admin'], storage);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Requires any of the specified roles for accessing a route.
|
|
42
|
+
*
|
|
43
|
+
* First checks if the user is authenticated, then verifies they have
|
|
44
|
+
* at least one of the required roles.
|
|
45
|
+
*
|
|
46
|
+
* @param request - Remix request object
|
|
47
|
+
* @param roles - Array of required roles (user must have at least one)
|
|
48
|
+
* @param storage - Optional custom session storage
|
|
49
|
+
* @returns User ID if authorized
|
|
50
|
+
* @throws Redirect to /login if not authenticated
|
|
51
|
+
* @throws 403 JSON response if missing required role
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
56
|
+
* // Require admin or editor role
|
|
57
|
+
* const userId = await requireRole(request, ['admin', 'editor']);
|
|
58
|
+
* return json({ content: await getContent() });
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
async function requireRole(request, roles, storage) {
|
|
63
|
+
// First ensure user is authenticated
|
|
64
|
+
const userId = await (0, session_server_js_1.requireAuth)(request, '/login', storage);
|
|
65
|
+
// Get user's groups from session
|
|
66
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
67
|
+
const userGroups = session.get('groups') ?? [];
|
|
68
|
+
// Check if user has at least one required role
|
|
69
|
+
const hasRequiredRole = roles.some((role) => userGroups.includes(role));
|
|
70
|
+
if (!hasRequiredRole) {
|
|
71
|
+
throw (0, node_1.json)(
|
|
72
|
+
{
|
|
73
|
+
error: {
|
|
74
|
+
code: 'FORBIDDEN',
|
|
75
|
+
message: `Required role: ${roles.join(' or ')}`,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{ status: 403 }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return userId;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Checks if the user has a specific role.
|
|
85
|
+
*
|
|
86
|
+
* Does not throw errors or redirect. Returns false for unauthenticated users.
|
|
87
|
+
*
|
|
88
|
+
* @param request - Remix request object
|
|
89
|
+
* @param role - Role to check
|
|
90
|
+
* @param storage - Optional custom session storage
|
|
91
|
+
* @returns True if user has role, false otherwise
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
96
|
+
* const isAdmin = await hasRole(request, 'admin');
|
|
97
|
+
* const canEdit = await hasRole(request, 'editor');
|
|
98
|
+
* return json({ isAdmin, canEdit });
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
async function hasRole(request, role, storage) {
|
|
103
|
+
try {
|
|
104
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
105
|
+
const userId = session.get('userId');
|
|
106
|
+
if (!userId) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const userGroups = session.get('groups') ?? [];
|
|
110
|
+
return userGroups.includes(role);
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Checks if the user has any of the specified roles.
|
|
117
|
+
*
|
|
118
|
+
* Does not throw errors or redirect. Returns false for unauthenticated users.
|
|
119
|
+
*
|
|
120
|
+
* @param request - Remix request object
|
|
121
|
+
* @param roles - Roles to check
|
|
122
|
+
* @param storage - Optional custom session storage
|
|
123
|
+
* @returns True if user has any role, false otherwise
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* export async function loader({ request }: LoaderFunctionArgs) {
|
|
128
|
+
* const canManageContent = await hasAnyRole(request, ['admin', 'editor']);
|
|
129
|
+
* return json({ canManageContent });
|
|
130
|
+
* }
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
async function hasAnyRole(request, roles, storage) {
|
|
134
|
+
try {
|
|
135
|
+
const session = await (0, session_server_js_1.getSession)(request, storage);
|
|
136
|
+
const userId = session.get('userId');
|
|
137
|
+
if (!userId) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const userGroups = session.get('groups') ?? [];
|
|
141
|
+
return roles.some((role) => userGroups.includes(role));
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=admin.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.server.js","sourceRoot":"","sources":["../../src/remix/admin.server.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AA2BH,oCAEC;AAwBD,kCA4BC;AAqBD,0BAkBC;AAoBD,gCAkBC;AA5JD,0CAAuC;AAEvC,2DAA8D;AAE9D;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,KAAK,UAAU,YAAY,CAAC,OAAgB,EAAE,OAAwB;IAC3E,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,KAAK,UAAU,WAAW,CAC/B,OAAgB,EAChB,KAAe,EACf,OAAwB;IAExB,qCAAqC;IACrC,MAAM,MAAM,GAAG,MAAM,IAAA,+BAAW,EAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE7D,iCAAiC;IACjC,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,UAAU,GAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAc,IAAI,EAAE,CAAC;IAE7D,+CAA+C;IAC/C,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAExE,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAA,WAAI,EACR;YACE,KAAK,EAAE;gBACL,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,kBAAkB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;aAChD;SACF,EACD,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACI,KAAK,UAAU,OAAO,CAC3B,OAAgB,EAChB,IAAY,EACZ,OAAwB;IAExB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAuB,CAAC;QAE3D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,UAAU,GAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAc,IAAI,EAAE,CAAC;QAC7D,OAAO,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACI,KAAK,UAAU,UAAU,CAC9B,OAAgB,EAChB,KAAe,EACf,OAAwB;IAExB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAuB,CAAC;QAE3D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,UAAU,GAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAc,IAAI,EAAE,CAAC;QAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remix authentication utilities exports.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
createSessionStorage,
|
|
8
|
+
getSession,
|
|
9
|
+
createUserSession,
|
|
10
|
+
requireAuth,
|
|
11
|
+
getOptionalUser,
|
|
12
|
+
getUserSession,
|
|
13
|
+
logout,
|
|
14
|
+
resetDefaultStorage,
|
|
15
|
+
} from './session.server.js';
|
|
16
|
+
export { requireAdmin, requireRole, hasRole, hasAnyRole } from './admin.server.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/remix/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,cAAc,EACd,MAAM,EACN,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Remix authentication utilities exports.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
8
|
+
exports.hasAnyRole =
|
|
9
|
+
exports.hasRole =
|
|
10
|
+
exports.requireRole =
|
|
11
|
+
exports.requireAdmin =
|
|
12
|
+
exports.resetDefaultStorage =
|
|
13
|
+
exports.logout =
|
|
14
|
+
exports.getUserSession =
|
|
15
|
+
exports.getOptionalUser =
|
|
16
|
+
exports.requireAuth =
|
|
17
|
+
exports.createUserSession =
|
|
18
|
+
exports.getSession =
|
|
19
|
+
exports.createSessionStorage =
|
|
20
|
+
void 0;
|
|
21
|
+
var session_server_js_1 = require('./session.server.js');
|
|
22
|
+
Object.defineProperty(exports, 'createSessionStorage', {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: function () {
|
|
25
|
+
return session_server_js_1.createSessionStorage;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(exports, 'getSession', {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
get: function () {
|
|
31
|
+
return session_server_js_1.getSession;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(exports, 'createUserSession', {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get: function () {
|
|
37
|
+
return session_server_js_1.createUserSession;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(exports, 'requireAuth', {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
get: function () {
|
|
43
|
+
return session_server_js_1.requireAuth;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(exports, 'getOptionalUser', {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
get: function () {
|
|
49
|
+
return session_server_js_1.getOptionalUser;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(exports, 'getUserSession', {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
get: function () {
|
|
55
|
+
return session_server_js_1.getUserSession;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(exports, 'logout', {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
get: function () {
|
|
61
|
+
return session_server_js_1.logout;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(exports, 'resetDefaultStorage', {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
get: function () {
|
|
67
|
+
return session_server_js_1.resetDefaultStorage;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
var admin_server_js_1 = require('./admin.server.js');
|
|
71
|
+
Object.defineProperty(exports, 'requireAdmin', {
|
|
72
|
+
enumerable: true,
|
|
73
|
+
get: function () {
|
|
74
|
+
return admin_server_js_1.requireAdmin;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
Object.defineProperty(exports, 'requireRole', {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
get: function () {
|
|
80
|
+
return admin_server_js_1.requireRole;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
Object.defineProperty(exports, 'hasRole', {
|
|
84
|
+
enumerable: true,
|
|
85
|
+
get: function () {
|
|
86
|
+
return admin_server_js_1.hasRole;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
Object.defineProperty(exports, 'hasAnyRole', {
|
|
90
|
+
enumerable: true,
|
|
91
|
+
get: function () {
|
|
92
|
+
return admin_server_js_1.hasAnyRole;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/remix/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,yDAS6B;AAR3B,yHAAA,oBAAoB,OAAA;AACpB,+GAAA,UAAU,OAAA;AACV,sHAAA,iBAAiB,OAAA;AACjB,gHAAA,WAAW,OAAA;AACX,oHAAA,eAAe,OAAA;AACf,mHAAA,cAAc,OAAA;AACd,2GAAA,MAAM,OAAA;AACN,wHAAA,mBAAmB,OAAA;AAGrB,qDAAmF;AAA1E,+GAAA,YAAY,OAAA;AAAE,8GAAA,WAAW,OAAA;AAAE,0GAAA,OAAO,OAAA;AAAE,6GAAA,UAAU,OAAA"}
|