@ahmedbaset/adminjs-hono 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/.eslintrc.cjs +20 -0
- package/README.md +239 -0
- package/examples/auth.ts +76 -0
- package/examples/simple.ts +42 -0
- package/lib/authentication/login.handler.d.ts +11 -0
- package/lib/authentication/login.handler.d.ts.map +1 -0
- package/lib/authentication/login.handler.js +155 -0
- package/lib/authentication/logout.handler.d.ts +11 -0
- package/lib/authentication/logout.handler.d.ts.map +1 -0
- package/lib/authentication/logout.handler.js +50 -0
- package/lib/authentication/protected-routes.handler.d.ts +11 -0
- package/lib/authentication/protected-routes.handler.d.ts.map +1 -0
- package/lib/authentication/protected-routes.handler.js +26 -0
- package/lib/authentication/refresh.handler.d.ts +13 -0
- package/lib/authentication/refresh.handler.d.ts.map +1 -0
- package/lib/authentication/refresh.handler.js +42 -0
- package/lib/buildAuthenticatedRouter.d.ts +15 -0
- package/lib/buildAuthenticatedRouter.d.ts.map +1 -0
- package/lib/buildAuthenticatedRouter.js +61 -0
- package/lib/buildRouter.d.ts +53 -0
- package/lib/buildRouter.d.ts.map +1 -0
- package/lib/buildRouter.js +178 -0
- package/lib/convertRoutes.d.ts +9 -0
- package/lib/convertRoutes.d.ts.map +1 -0
- package/lib/convertRoutes.js +10 -0
- package/lib/errors.d.ts +10 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +15 -0
- package/lib/formParser.d.ts +13 -0
- package/lib/formParser.d.ts.map +1 -0
- package/lib/formParser.js +53 -0
- package/lib/index.d.ts +55 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +48 -0
- package/lib/logger.d.ts +7 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/logger.js +17 -0
- package/lib/session.d.ts +25 -0
- package/lib/session.d.ts.map +1 -0
- package/lib/session.js +56 -0
- package/lib/types.d.ts +46 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +1 -0
- package/package.json +44 -0
- package/src/authentication/login.handler.ts +193 -0
- package/src/authentication/logout.handler.ts +62 -0
- package/src/authentication/protected-routes.handler.ts +38 -0
- package/src/authentication/refresh.handler.ts +59 -0
- package/src/buildAuthenticatedRouter.ts +92 -0
- package/src/buildRouter.ts +224 -0
- package/src/convertRoutes.ts +10 -0
- package/src/errors.ts +24 -0
- package/src/formParser.ts +73 -0
- package/src/index.ts +74 -0
- package/src/logger.ts +18 -0
- package/src/session.ts +71 -0
- package/src/types.ts +53 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers token refresh route with the Hono app
|
|
3
|
+
* Delegates to authentication provider's refresh method if available
|
|
4
|
+
*
|
|
5
|
+
* @param app - Hono app instance
|
|
6
|
+
* @param admin - AdminJS instance
|
|
7
|
+
* @param auth - Authentication options
|
|
8
|
+
*/
|
|
9
|
+
export function withRefresh(app, admin, auth) {
|
|
10
|
+
const { provider } = auth;
|
|
11
|
+
// Only register refresh route if provider supports it
|
|
12
|
+
if (!provider || !provider.handleRefresh) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Typically refresh is at /refresh, but this depends on the provider
|
|
16
|
+
const refreshPath = '/refresh';
|
|
17
|
+
app.post(refreshPath, async (c) => {
|
|
18
|
+
try {
|
|
19
|
+
const fields = c.get('fields') || {};
|
|
20
|
+
const session = c.get('session');
|
|
21
|
+
// Call provider's refresh method
|
|
22
|
+
const refreshedUser = await provider.handleRefresh({
|
|
23
|
+
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
24
|
+
query: c.req.query(),
|
|
25
|
+
params: c.req.param(),
|
|
26
|
+
data: fields,
|
|
27
|
+
}, { req: c, res: c });
|
|
28
|
+
if (refreshedUser) {
|
|
29
|
+
// Update session with new credentials
|
|
30
|
+
session.adminUser = refreshedUser;
|
|
31
|
+
return c.json({ success: true });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return c.json({ success: false, error: 'Refresh failed' }, 401);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('Token refresh error:', error);
|
|
39
|
+
return c.json({ success: false, error: error.message || 'Refresh failed' }, 401);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import AdminJS from 'adminjs';
|
|
2
|
+
import type { Hono } from 'hono';
|
|
3
|
+
import type { AuthenticationOptions, SessionOptions, UploadOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds a Hono app with AdminJS routes protected by session-based authentication
|
|
6
|
+
*
|
|
7
|
+
* @param admin - The AdminJS instance
|
|
8
|
+
* @param auth - Authentication configuration
|
|
9
|
+
* @param predefinedApp - Optional existing Hono app to use
|
|
10
|
+
* @param sessionOptions - Optional session configuration
|
|
11
|
+
* @param uploadOptions - Optional upload configuration
|
|
12
|
+
* @returns Configured Hono app with authentication
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildAuthenticatedRouter(admin: AdminJS, auth: AuthenticationOptions, predefinedApp?: Hono, sessionOptions?: SessionOptions, uploadOptions?: UploadOptions): Hono;
|
|
15
|
+
//# sourceMappingURL=buildAuthenticatedRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildAuthenticatedRouter.d.ts","sourceRoot":"","sources":["../src/buildAuthenticatedRouter.ts"],"names":[],"mappings":"AAAA,OAAO,OAAkC,MAAM,SAAS,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAWhC,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAItF;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,qBAAqB,EAC3B,aAAa,CAAC,EAAE,IAAI,EACpB,cAAc,CAAC,EAAE,cAAc,EAC/B,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI,CA2DN"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Router as AdminRouter } from 'adminjs';
|
|
2
|
+
import { withLogin } from './authentication/login.handler.js';
|
|
3
|
+
import { withLogout } from './authentication/logout.handler.js';
|
|
4
|
+
import { withProtectedRoutesHandler } from './authentication/protected-routes.handler.js';
|
|
5
|
+
import { withRefresh } from './authentication/refresh.handler.js';
|
|
6
|
+
import { buildAssets, buildRoutes, initializeAdmin } from './buildRouter.js';
|
|
7
|
+
import { INVALID_AUTH_CONFIG_ERROR, MISSING_AUTH_CONFIG_ERROR, WrongArgumentError, } from './errors.js';
|
|
8
|
+
import { createFormParserMiddleware } from './formParser.js';
|
|
9
|
+
import { createSessionMiddleware } from './session.js';
|
|
10
|
+
/**
|
|
11
|
+
* Builds a Hono app with AdminJS routes protected by session-based authentication
|
|
12
|
+
*
|
|
13
|
+
* @param admin - The AdminJS instance
|
|
14
|
+
* @param auth - Authentication configuration
|
|
15
|
+
* @param predefinedApp - Optional existing Hono app to use
|
|
16
|
+
* @param sessionOptions - Optional session configuration
|
|
17
|
+
* @param uploadOptions - Optional upload configuration
|
|
18
|
+
* @returns Configured Hono app with authentication
|
|
19
|
+
*/
|
|
20
|
+
export function buildAuthenticatedRouter(admin, auth, predefinedApp, sessionOptions, uploadOptions) {
|
|
21
|
+
// Initialize AdminJS
|
|
22
|
+
initializeAdmin(admin);
|
|
23
|
+
// Validate authentication configuration
|
|
24
|
+
if (!auth.authenticate && !auth.provider) {
|
|
25
|
+
throw new WrongArgumentError(MISSING_AUTH_CONFIG_ERROR);
|
|
26
|
+
}
|
|
27
|
+
if (auth.authenticate && auth.provider) {
|
|
28
|
+
throw new WrongArgumentError(INVALID_AUTH_CONFIG_ERROR);
|
|
29
|
+
}
|
|
30
|
+
// Use provided app or create new Hono instance
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
32
|
+
const { Hono: HonoClass } = require('hono');
|
|
33
|
+
const app = predefinedApp ?? new HonoClass();
|
|
34
|
+
// Get routes and assets from AdminJS
|
|
35
|
+
const { routes, assets } = AdminRouter;
|
|
36
|
+
// If provider is configured, add its UI props to AdminJS env
|
|
37
|
+
if (auth.provider) {
|
|
38
|
+
admin.options.env = {
|
|
39
|
+
...admin.options.env,
|
|
40
|
+
...auth.provider.getUiProps(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Configure session middleware
|
|
44
|
+
const cookieName = auth.cookieName || 'adminjs';
|
|
45
|
+
app.use('*', createSessionMiddleware(auth.cookiePassword, cookieName, sessionOptions));
|
|
46
|
+
// Register form parsing middleware
|
|
47
|
+
app.use('*', createFormParserMiddleware(uploadOptions));
|
|
48
|
+
// Register login handler (must be before protected routes middleware)
|
|
49
|
+
withLogin(app, admin, auth);
|
|
50
|
+
// Register logout handler (must be before protected routes middleware)
|
|
51
|
+
withLogout(app, admin, auth);
|
|
52
|
+
// Build assets (must be before protected routes middleware)
|
|
53
|
+
buildAssets(assets, routes, app, admin);
|
|
54
|
+
// Register protected routes middleware (applies to all subsequent routes)
|
|
55
|
+
withProtectedRoutesHandler(app, admin);
|
|
56
|
+
// Register refresh handler (after protected routes middleware)
|
|
57
|
+
withRefresh(app, admin, auth);
|
|
58
|
+
// Build routes (after protected routes middleware)
|
|
59
|
+
buildRoutes(routes, app, admin);
|
|
60
|
+
return app;
|
|
61
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import AdminJS, { Router as AdminRouter } from 'adminjs';
|
|
2
|
+
import type { Hono, Handler } from 'hono';
|
|
3
|
+
import type { HonoVariables } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Validates and initializes an AdminJS instance
|
|
6
|
+
* @param admin - The AdminJS instance to initialize
|
|
7
|
+
* @throws {WrongArgumentError} If the admin parameter is not a valid AdminJS instance
|
|
8
|
+
*/
|
|
9
|
+
export declare function initializeAdmin(admin: AdminJS): void;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a Hono route handler from an AdminJS route
|
|
12
|
+
* Extracts request data and invokes the AdminJS controller
|
|
13
|
+
*
|
|
14
|
+
* @param admin - The AdminJS instance
|
|
15
|
+
* @param route - The AdminJS route definition
|
|
16
|
+
* @returns Hono handler function
|
|
17
|
+
*/
|
|
18
|
+
export declare function routeHandler(admin: AdminJS, route: (typeof AdminRouter)['routes'][0]): Handler<{
|
|
19
|
+
Variables: HonoVariables;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Registers a single AdminJS route with the Hono app
|
|
23
|
+
* @param route - The AdminJS route to register
|
|
24
|
+
* @param app - The Hono app instance
|
|
25
|
+
* @param admin - The AdminJS instance
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildRoute(route: (typeof AdminRouter)['routes'][number], app: Hono, admin: AdminJS): void;
|
|
28
|
+
/**
|
|
29
|
+
* Registers all AdminJS routes with the Hono app
|
|
30
|
+
* @param routes - Array of AdminJS routes
|
|
31
|
+
* @param app - The Hono app instance
|
|
32
|
+
* @param admin - The AdminJS instance
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildRoutes(routes: (typeof AdminRouter)['routes'], app: Hono, admin: AdminJS): void;
|
|
35
|
+
/**
|
|
36
|
+
* Registers AdminJS asset routes with the Hono app
|
|
37
|
+
* @param assets - Array of AdminJS assets
|
|
38
|
+
* @param routes - Array of AdminJS routes (to find component bundler route)
|
|
39
|
+
* @param app - The Hono app instance
|
|
40
|
+
* @param admin - The AdminJS instance
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildAssets(assets: (typeof AdminRouter)['assets'], routes: (typeof AdminRouter)['routes'], app: Hono, admin: AdminJS): void;
|
|
43
|
+
import type { UploadOptions } from './types.js';
|
|
44
|
+
/**
|
|
45
|
+
* Builds a Hono app with AdminJS routes (without authentication)
|
|
46
|
+
*
|
|
47
|
+
* @param admin - The AdminJS instance
|
|
48
|
+
* @param predefinedApp - Optional existing Hono app to use
|
|
49
|
+
* @param uploadOptions - Optional upload configuration
|
|
50
|
+
* @returns Configured Hono app
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildRouter(admin: AdminJS, predefinedApp?: Hono, uploadOptions?: UploadOptions): Hono;
|
|
53
|
+
//# sourceMappingURL=buildRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildRouter.d.ts","sourceRoot":"","sources":["../src/buildRouter.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAW,MAAM,MAAM,CAAA;AAIlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAQpD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,CAAC,CA+CvC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAC7C,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GACb,IAAI,CAUN;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,EACtC,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GACb,IAAI,CAEN;AA0BD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,EACtC,MAAM,EAAE,CAAC,OAAO,WAAW,CAAC,CAAC,QAAQ,CAAC,EACtC,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GACb,IAAI,CAgCN;AAED,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG/C;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,aAAa,CAAC,EAAE,IAAI,EACpB,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI,CAoBN"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { Router as AdminRouter } from 'adminjs';
|
|
2
|
+
import { WrongArgumentError, INVALID_ADMINJS_INSTANCE } from './errors.js';
|
|
3
|
+
import { log } from './logger.js';
|
|
4
|
+
import { convertToHonoRoute } from './convertRoutes.js';
|
|
5
|
+
/**
|
|
6
|
+
* Validates and initializes an AdminJS instance
|
|
7
|
+
* @param admin - The AdminJS instance to initialize
|
|
8
|
+
* @throws {WrongArgumentError} If the admin parameter is not a valid AdminJS instance
|
|
9
|
+
*/
|
|
10
|
+
export function initializeAdmin(admin) {
|
|
11
|
+
if (admin?.constructor?.name !== 'AdminJS') {
|
|
12
|
+
throw new WrongArgumentError(INVALID_ADMINJS_INSTANCE);
|
|
13
|
+
}
|
|
14
|
+
admin.initialize().then(() => {
|
|
15
|
+
log.debug('AdminJS: bundle ready');
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a Hono route handler from an AdminJS route
|
|
20
|
+
* Extracts request data and invokes the AdminJS controller
|
|
21
|
+
*
|
|
22
|
+
* @param admin - The AdminJS instance
|
|
23
|
+
* @param route - The AdminJS route definition
|
|
24
|
+
* @returns Hono handler function
|
|
25
|
+
*/
|
|
26
|
+
export function routeHandler(admin, route) {
|
|
27
|
+
return async (c) => {
|
|
28
|
+
// Get session from context (may be undefined for non-authenticated routes)
|
|
29
|
+
const session = c.get('session');
|
|
30
|
+
const adminUser = session?.adminUser;
|
|
31
|
+
// Instantiate the AdminJS controller
|
|
32
|
+
const controller = new route.Controller({ admin }, adminUser);
|
|
33
|
+
// Extract request data
|
|
34
|
+
const params = c.req.param();
|
|
35
|
+
const query = c.req.query();
|
|
36
|
+
const method = c.req.method.toLowerCase();
|
|
37
|
+
// Get parsed form data from context (set by form parser middleware)
|
|
38
|
+
const fields = c.get('fields') || {};
|
|
39
|
+
const files = c.get('files') || {};
|
|
40
|
+
const payload = {
|
|
41
|
+
...fields,
|
|
42
|
+
...files,
|
|
43
|
+
};
|
|
44
|
+
// Execute the controller action
|
|
45
|
+
const html = await controller[route.action]({
|
|
46
|
+
...c.req,
|
|
47
|
+
params,
|
|
48
|
+
query,
|
|
49
|
+
payload,
|
|
50
|
+
method,
|
|
51
|
+
}, c.res);
|
|
52
|
+
// Set Content-Type header if specified
|
|
53
|
+
if (route.contentType) {
|
|
54
|
+
c.header('Content-Type', route.contentType);
|
|
55
|
+
}
|
|
56
|
+
// Return response
|
|
57
|
+
if (html) {
|
|
58
|
+
return c.body(html);
|
|
59
|
+
}
|
|
60
|
+
// If no response body, return empty response
|
|
61
|
+
return c.body(null);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Registers a single AdminJS route with the Hono app
|
|
66
|
+
* @param route - The AdminJS route to register
|
|
67
|
+
* @param app - The Hono app instance
|
|
68
|
+
* @param admin - The AdminJS instance
|
|
69
|
+
*/
|
|
70
|
+
export function buildRoute(route, app, admin) {
|
|
71
|
+
// Convert AdminJS route path to Hono format
|
|
72
|
+
const honoPath = convertToHonoRoute(route.path);
|
|
73
|
+
// Register handler based on HTTP method
|
|
74
|
+
if (route.method === 'GET') {
|
|
75
|
+
app.get(honoPath, routeHandler(admin, route));
|
|
76
|
+
}
|
|
77
|
+
else if (route.method === 'POST') {
|
|
78
|
+
app.post(honoPath, routeHandler(admin, route));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Registers all AdminJS routes with the Hono app
|
|
83
|
+
* @param routes - Array of AdminJS routes
|
|
84
|
+
* @param app - The Hono app instance
|
|
85
|
+
* @param admin - The AdminJS instance
|
|
86
|
+
*/
|
|
87
|
+
export function buildRoutes(routes, app, admin) {
|
|
88
|
+
routes.forEach((route) => buildRoute(route, app, admin));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Determines Content-Type header based on file extension
|
|
92
|
+
* @param path - File path
|
|
93
|
+
* @returns Content-Type string
|
|
94
|
+
*/
|
|
95
|
+
function getContentType(path) {
|
|
96
|
+
if (path.endsWith('.js')) {
|
|
97
|
+
return 'application/javascript';
|
|
98
|
+
}
|
|
99
|
+
else if (path.endsWith('.css')) {
|
|
100
|
+
return 'text/css';
|
|
101
|
+
}
|
|
102
|
+
else if (path.endsWith('.html')) {
|
|
103
|
+
return 'text/html';
|
|
104
|
+
}
|
|
105
|
+
else if (path.endsWith('.json')) {
|
|
106
|
+
return 'application/json';
|
|
107
|
+
}
|
|
108
|
+
else if (path.endsWith('.png')) {
|
|
109
|
+
return 'image/png';
|
|
110
|
+
}
|
|
111
|
+
else if (path.endsWith('.jpg') || path.endsWith('.jpeg')) {
|
|
112
|
+
return 'image/jpeg';
|
|
113
|
+
}
|
|
114
|
+
else if (path.endsWith('.svg')) {
|
|
115
|
+
return 'image/svg+xml';
|
|
116
|
+
}
|
|
117
|
+
return 'application/octet-stream';
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Registers AdminJS asset routes with the Hono app
|
|
121
|
+
* @param assets - Array of AdminJS assets
|
|
122
|
+
* @param routes - Array of AdminJS routes (to find component bundler route)
|
|
123
|
+
* @param app - The Hono app instance
|
|
124
|
+
* @param admin - The AdminJS instance
|
|
125
|
+
*/
|
|
126
|
+
export function buildAssets(assets, routes, app, admin) {
|
|
127
|
+
// Register component bundler route if it exists
|
|
128
|
+
const componentBundlerRoute = routes.find((r) => r.action === 'bundleComponents');
|
|
129
|
+
if (componentBundlerRoute) {
|
|
130
|
+
buildRoute(componentBundlerRoute, app, admin);
|
|
131
|
+
}
|
|
132
|
+
// Register static asset routes
|
|
133
|
+
assets.forEach((asset) => {
|
|
134
|
+
app.get(asset.path, async (c) => {
|
|
135
|
+
try {
|
|
136
|
+
// Read file using Node.js fs (for Node runtime)
|
|
137
|
+
// For other runtimes, this would need runtime detection
|
|
138
|
+
const fs = await import('fs/promises');
|
|
139
|
+
const path = await import('path');
|
|
140
|
+
const filePath = path.resolve(asset.src);
|
|
141
|
+
const fileContent = await fs.readFile(filePath);
|
|
142
|
+
// Set appropriate Content-Type header
|
|
143
|
+
const contentType = getContentType(asset.path);
|
|
144
|
+
c.header('Content-Type', contentType);
|
|
145
|
+
return c.body(fileContent);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error(`Error serving asset ${asset.path}:`, error);
|
|
149
|
+
return c.text('Asset not found', 404);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
import { createFormParserMiddleware } from './formParser.js';
|
|
155
|
+
/**
|
|
156
|
+
* Builds a Hono app with AdminJS routes (without authentication)
|
|
157
|
+
*
|
|
158
|
+
* @param admin - The AdminJS instance
|
|
159
|
+
* @param predefinedApp - Optional existing Hono app to use
|
|
160
|
+
* @param uploadOptions - Optional upload configuration
|
|
161
|
+
* @returns Configured Hono app
|
|
162
|
+
*/
|
|
163
|
+
export function buildRouter(admin, predefinedApp, uploadOptions) {
|
|
164
|
+
// Initialize AdminJS
|
|
165
|
+
initializeAdmin(admin);
|
|
166
|
+
// Use provided app or create new Hono instance
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
168
|
+
const { Hono: HonoClass } = require('hono');
|
|
169
|
+
const app = predefinedApp ?? new HonoClass();
|
|
170
|
+
// Get routes and assets from AdminJS
|
|
171
|
+
const { routes, assets } = AdminRouter;
|
|
172
|
+
// Register form parsing middleware
|
|
173
|
+
app.use('*', createFormParserMiddleware(uploadOptions));
|
|
174
|
+
// Build assets and routes
|
|
175
|
+
buildAssets(assets, routes, app, admin);
|
|
176
|
+
buildRoutes(routes, app, admin);
|
|
177
|
+
return app;
|
|
178
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts AdminJS route path format to Hono route path format
|
|
3
|
+
* AdminJS uses {param} notation while Hono uses :param notation
|
|
4
|
+
*
|
|
5
|
+
* @param path - AdminJS route path (e.g., "/resources/{resourceId}/actions/{action}")
|
|
6
|
+
* @returns Hono route path (e.g., "/resources/:resourceId/actions/:action")
|
|
7
|
+
*/
|
|
8
|
+
export declare function convertToHonoRoute(path: string): string;
|
|
9
|
+
//# sourceMappingURL=convertRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convertRoutes.d.ts","sourceRoot":"","sources":["../src/convertRoutes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts AdminJS route path format to Hono route path format
|
|
3
|
+
* AdminJS uses {param} notation while Hono uses :param notation
|
|
4
|
+
*
|
|
5
|
+
* @param path - AdminJS route path (e.g., "/resources/{resourceId}/actions/{action}")
|
|
6
|
+
* @returns Hono route path (e.g., "/resources/:resourceId/actions/:action")
|
|
7
|
+
*/
|
|
8
|
+
export function convertToHonoRoute(path) {
|
|
9
|
+
return path.replace(/\{([^}]+)\}/g, ':$1');
|
|
10
|
+
}
|
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class WrongArgumentError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare const INVALID_ADMINJS_INSTANCE = "You have to pass an instance of AdminJS to the buildRouter() function";
|
|
5
|
+
export declare const MISSING_AUTH_CONFIG_ERROR = "You must provide either \"authenticate\" function or \"provider\" in authentication options";
|
|
6
|
+
export declare const INVALID_AUTH_CONFIG_ERROR = "You cannot provide both \"authenticate\" function and \"provider\" in authentication options";
|
|
7
|
+
export declare class OldBodyParserUsedError extends Error {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,wBAAwB,0EACoC,CAAA;AAEzE,eAAO,MAAM,yBAAyB,gGACqD,CAAA;AAE3F,eAAO,MAAM,yBAAyB,iGACsD,CAAA;AAE5F,qBAAa,sBAAuB,SAAQ,KAAK;;CAOhD"}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class WrongArgumentError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'WrongArgumentError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export const INVALID_ADMINJS_INSTANCE = 'You have to pass an instance of AdminJS to the buildRouter() function';
|
|
8
|
+
export const MISSING_AUTH_CONFIG_ERROR = 'You must provide either "authenticate" function or "provider" in authentication options';
|
|
9
|
+
export const INVALID_AUTH_CONFIG_ERROR = 'You cannot provide both "authenticate" function and "provider" in authentication options';
|
|
10
|
+
export class OldBodyParserUsedError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super('You are using old body-parser middleware which is not compatible with AdminJS. Please remove it.');
|
|
13
|
+
this.name = 'OldBodyParserUsedError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { UploadOptions, HonoVariables } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates middleware to parse form data (multipart/form-data and application/x-www-form-urlencoded)
|
|
5
|
+
* Extracts fields and files from the request and stores them in context variables
|
|
6
|
+
*
|
|
7
|
+
* @param options - Upload configuration options
|
|
8
|
+
* @returns Hono middleware handler
|
|
9
|
+
*/
|
|
10
|
+
export declare function createFormParserMiddleware(options?: UploadOptions): MiddlewareHandler<{
|
|
11
|
+
Variables: HonoVariables;
|
|
12
|
+
}>;
|
|
13
|
+
//# sourceMappingURL=formParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formParser.d.ts","sourceRoot":"","sources":["../src/formParser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE9D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,CAAC,EAAE,aAAa,GACtB,iBAAiB,CAAC;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,CAAC,CA4DjD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates middleware to parse form data (multipart/form-data and application/x-www-form-urlencoded)
|
|
3
|
+
* Extracts fields and files from the request and stores them in context variables
|
|
4
|
+
*
|
|
5
|
+
* @param options - Upload configuration options
|
|
6
|
+
* @returns Hono middleware handler
|
|
7
|
+
*/
|
|
8
|
+
export function createFormParserMiddleware(options) {
|
|
9
|
+
return async (c, next) => {
|
|
10
|
+
const contentType = c.req.header('content-type');
|
|
11
|
+
if (contentType?.includes('multipart/form-data') ||
|
|
12
|
+
contentType?.includes('application/x-www-form-urlencoded')) {
|
|
13
|
+
try {
|
|
14
|
+
const formData = await c.req.formData();
|
|
15
|
+
const fields = {};
|
|
16
|
+
const files = {};
|
|
17
|
+
for (const [key, value] of formData.entries()) {
|
|
18
|
+
if (value instanceof File) {
|
|
19
|
+
// Check file size if maxFileSize is specified
|
|
20
|
+
if (options?.maxFileSize && value.size > options.maxFileSize) {
|
|
21
|
+
throw new Error(`File ${value.name} exceeds maximum size of ${options.maxFileSize} bytes`);
|
|
22
|
+
}
|
|
23
|
+
files[key] = value;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
fields[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Check total fields size if maxFieldsSize is specified
|
|
30
|
+
if (options?.maxFieldsSize) {
|
|
31
|
+
const totalFieldsSize = Object.values(fields).reduce((sum, val) => sum + String(val).length, 0);
|
|
32
|
+
if (totalFieldsSize > options.maxFieldsSize) {
|
|
33
|
+
throw new Error(`Total fields size exceeds maximum of ${options.maxFieldsSize} bytes`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Check number of fields if maxFields is specified
|
|
37
|
+
if (options?.maxFields && Object.keys(fields).length > options.maxFields) {
|
|
38
|
+
throw new Error(`Number of fields exceeds maximum of ${options.maxFields}`);
|
|
39
|
+
}
|
|
40
|
+
c.set('fields', fields);
|
|
41
|
+
c.set('files', files);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Handle parsing errors gracefully
|
|
45
|
+
console.error('Form parsing error:', error);
|
|
46
|
+
c.set('fields', {});
|
|
47
|
+
c.set('files', {});
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await next();
|
|
52
|
+
};
|
|
53
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { buildAuthenticatedRouter } from './buildAuthenticatedRouter.js';
|
|
2
|
+
import { buildRouter } from './buildRouter.js';
|
|
3
|
+
/**
|
|
4
|
+
* @module @adminjs/hono
|
|
5
|
+
*
|
|
6
|
+
* AdminJS adapter for Hono web framework
|
|
7
|
+
*
|
|
8
|
+
* Provides two main functions:
|
|
9
|
+
* - buildRouter: Creates a Hono app with AdminJS routes (no authentication)
|
|
10
|
+
* - buildAuthenticatedRouter: Creates a Hono app with AdminJS routes protected by session authentication
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { Hono } from 'hono'
|
|
15
|
+
* import AdminJS from 'adminjs'
|
|
16
|
+
* import { buildRouter } from '@adminjs/hono'
|
|
17
|
+
*
|
|
18
|
+
* const admin = new AdminJS({
|
|
19
|
+
* databases: [],
|
|
20
|
+
* rootPath: '/admin',
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* const app = new Hono()
|
|
24
|
+
* const adminRouter = buildRouter(admin)
|
|
25
|
+
* app.route('/admin', adminRouter)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Plugin name
|
|
30
|
+
*/
|
|
31
|
+
export declare const name = "AdminJSHono";
|
|
32
|
+
/**
|
|
33
|
+
* Plugin interface
|
|
34
|
+
*/
|
|
35
|
+
export type HonoPlugin = {
|
|
36
|
+
name: string;
|
|
37
|
+
buildAuthenticatedRouter: typeof buildAuthenticatedRouter;
|
|
38
|
+
buildRouter: typeof buildRouter;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Default plugin export
|
|
42
|
+
*/
|
|
43
|
+
declare const plugin: HonoPlugin;
|
|
44
|
+
export default plugin;
|
|
45
|
+
export { buildRouter } from './buildRouter.js';
|
|
46
|
+
export { buildAuthenticatedRouter } from './buildAuthenticatedRouter.js';
|
|
47
|
+
export type { AuthenticationOptions, UploadOptions, AuthenticationMaxRetriesOptions, AuthenticationContext, SessionOptions, SessionData, } from './types.js';
|
|
48
|
+
export { convertToHonoRoute } from './convertRoutes.js';
|
|
49
|
+
export * from './errors.js';
|
|
50
|
+
export { log } from './logger.js';
|
|
51
|
+
export { withLogin } from './authentication/login.handler.js';
|
|
52
|
+
export { withLogout } from './authentication/logout.handler.js';
|
|
53
|
+
export { withProtectedRoutesHandler } from './authentication/protected-routes.handler.js';
|
|
54
|
+
export { withRefresh } from './authentication/refresh.handler.js';
|
|
55
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;GAEG;AACH,eAAO,MAAM,IAAI,gBAAgB,CAAA;AAEjC;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB,EAAE,OAAO,wBAAwB,CAAA;IACzD,WAAW,EAAE,OAAO,WAAW,CAAA;CAChC,CAAA;AAED;;GAEG;AACH,QAAA,MAAM,MAAM,EAAE,UAA4D,CAAA;AAE1E,eAAe,MAAM,CAAA;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAGxE,YAAY,EACV,qBAAqB,EACrB,aAAa,EACb,+BAA+B,EAC/B,qBAAqB,EACrB,cAAc,EACd,WAAW,GACZ,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,cAAc,aAAa,CAAA;AAC3B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8CAA8C,CAAA;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { buildAuthenticatedRouter } from './buildAuthenticatedRouter.js';
|
|
2
|
+
import { buildRouter } from './buildRouter.js';
|
|
3
|
+
/**
|
|
4
|
+
* @module @adminjs/hono
|
|
5
|
+
*
|
|
6
|
+
* AdminJS adapter for Hono web framework
|
|
7
|
+
*
|
|
8
|
+
* Provides two main functions:
|
|
9
|
+
* - buildRouter: Creates a Hono app with AdminJS routes (no authentication)
|
|
10
|
+
* - buildAuthenticatedRouter: Creates a Hono app with AdminJS routes protected by session authentication
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { Hono } from 'hono'
|
|
15
|
+
* import AdminJS from 'adminjs'
|
|
16
|
+
* import { buildRouter } from '@adminjs/hono'
|
|
17
|
+
*
|
|
18
|
+
* const admin = new AdminJS({
|
|
19
|
+
* databases: [],
|
|
20
|
+
* rootPath: '/admin',
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* const app = new Hono()
|
|
24
|
+
* const adminRouter = buildRouter(admin)
|
|
25
|
+
* app.route('/admin', adminRouter)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Plugin name
|
|
30
|
+
*/
|
|
31
|
+
export const name = 'AdminJSHono';
|
|
32
|
+
/**
|
|
33
|
+
* Default plugin export
|
|
34
|
+
*/
|
|
35
|
+
const plugin = { name, buildAuthenticatedRouter, buildRouter };
|
|
36
|
+
export default plugin;
|
|
37
|
+
// Export main functions
|
|
38
|
+
export { buildRouter } from './buildRouter.js';
|
|
39
|
+
export { buildAuthenticatedRouter } from './buildAuthenticatedRouter.js';
|
|
40
|
+
// Export utilities
|
|
41
|
+
export { convertToHonoRoute } from './convertRoutes.js';
|
|
42
|
+
export * from './errors.js';
|
|
43
|
+
export { log } from './logger.js';
|
|
44
|
+
// Export authentication handlers (for advanced usage)
|
|
45
|
+
export { withLogin } from './authentication/login.handler.js';
|
|
46
|
+
export { withLogout } from './authentication/logout.handler.js';
|
|
47
|
+
export { withProtectedRoutesHandler } from './authentication/protected-routes.handler.js';
|
|
48
|
+
export { withRefresh } from './authentication/refresh.handler.js';
|
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;qBACG,OAAO,EAAE;oBAKV,OAAO,EAAE;oBAGT,OAAO,EAAE;qBAGR,OAAO,EAAE;CAG3B,CAAA"}
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const DEBUG = process.env.ADMINJS_HONO_DEBUG === 'true';
|
|
2
|
+
export const log = {
|
|
3
|
+
debug: (...args) => {
|
|
4
|
+
if (DEBUG) {
|
|
5
|
+
console.log('[AdminJS Hono]', ...args);
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
info: (...args) => {
|
|
9
|
+
console.log('[AdminJS Hono]', ...args);
|
|
10
|
+
},
|
|
11
|
+
warn: (...args) => {
|
|
12
|
+
console.warn('[AdminJS Hono]', ...args);
|
|
13
|
+
},
|
|
14
|
+
error: (...args) => {
|
|
15
|
+
console.error('[AdminJS Hono]', ...args);
|
|
16
|
+
},
|
|
17
|
+
};
|
package/lib/session.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { SessionData, SessionOptions, HonoVariables } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates session middleware for Hono
|
|
5
|
+
* Manages cookie-based sessions with in-memory storage
|
|
6
|
+
*
|
|
7
|
+
* @param secret - Secret key for session (currently unused, for future HMAC signing)
|
|
8
|
+
* @param cookieName - Name of the session cookie
|
|
9
|
+
* @param options - Session cookie options
|
|
10
|
+
* @returns Hono middleware handler
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSessionMiddleware(secret: string, cookieName: string, options?: SessionOptions): MiddlewareHandler<{
|
|
13
|
+
Variables: HonoVariables;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Destroys a session by removing it from the store
|
|
17
|
+
* @param sessionId - The session ID to destroy
|
|
18
|
+
*/
|
|
19
|
+
export declare function destroySession(sessionId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Gets the session store (for testing purposes)
|
|
22
|
+
* @returns The session store Map
|
|
23
|
+
*/
|
|
24
|
+
export declare function getSessionStore(): Map<string, SessionData>;
|
|
25
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAa5E;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,cAAc,GACvB,iBAAiB,CAAC;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,CAAC,CA0BjD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAE1D"}
|