@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
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
extends: [
|
|
4
|
+
'eslint:recommended',
|
|
5
|
+
'plugin:@typescript-eslint/recommended',
|
|
6
|
+
],
|
|
7
|
+
plugins: ['@typescript-eslint'],
|
|
8
|
+
env: {
|
|
9
|
+
node: true,
|
|
10
|
+
es2022: true,
|
|
11
|
+
},
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
18
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
19
|
+
},
|
|
20
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# @ahmedbaset/adminjs-hono
|
|
2
|
+
|
|
3
|
+
AdminJS adapter for [Hono](https://hono.dev/) web framework. This adapter allows you to integrate AdminJS admin panels into Hono applications with support for all JavaScript runtimes (Node.js, Deno, Bun, Cloudflare Workers, etc.).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ahmedbaset/adminjs-hono adminjs hono
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🚀 Works with all Hono-supported runtimes (Node.js, Deno, Bun, Cloudflare Workers, etc.)
|
|
14
|
+
- 🔒 Built-in session-based authentication
|
|
15
|
+
- 📦 File upload support using Web Standards
|
|
16
|
+
- 🎨 Same API design as the Express adapter for easy migration
|
|
17
|
+
- 📝 Full TypeScript support
|
|
18
|
+
- ⚡ Lightweight and fast
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Usage (No Authentication)
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Hono } from 'hono'
|
|
26
|
+
import AdminJS from 'adminjs'
|
|
27
|
+
import { buildRouter } from '@ahmedbaset/adminjs-hono'
|
|
28
|
+
|
|
29
|
+
const admin = new AdminJS({
|
|
30
|
+
databases: [],
|
|
31
|
+
rootPath: '/admin',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const app = new Hono()
|
|
35
|
+
const adminRouter = buildRouter(admin)
|
|
36
|
+
|
|
37
|
+
app.route('/admin', adminRouter)
|
|
38
|
+
|
|
39
|
+
export default app
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### With Authentication
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { Hono } from 'hono'
|
|
46
|
+
import AdminJS from 'adminjs'
|
|
47
|
+
import { buildAuthenticatedRouter } from '@ahmedbaset/adminjs-hono'
|
|
48
|
+
|
|
49
|
+
const admin = new AdminJS({
|
|
50
|
+
databases: [],
|
|
51
|
+
rootPath: '/admin',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const app = new Hono()
|
|
55
|
+
|
|
56
|
+
const adminRouter = buildAuthenticatedRouter(
|
|
57
|
+
admin,
|
|
58
|
+
{
|
|
59
|
+
authenticate: async (email, password) => {
|
|
60
|
+
// Verify credentials against your database
|
|
61
|
+
if (email === 'admin@example.com' && password === 'password') {
|
|
62
|
+
return { email, name: 'Admin' }
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
},
|
|
66
|
+
cookiePassword: 'some-secret-password-at-least-32-characters-long',
|
|
67
|
+
cookieName: 'adminjs',
|
|
68
|
+
},
|
|
69
|
+
undefined, // predefined app (optional)
|
|
70
|
+
{
|
|
71
|
+
// Session options
|
|
72
|
+
maxAge: 86400, // 24 hours
|
|
73
|
+
httpOnly: true,
|
|
74
|
+
secure: process.env.NODE_ENV === 'production',
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
app.route('/admin', adminRouter)
|
|
79
|
+
|
|
80
|
+
export default app
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Using an Existing Hono App
|
|
84
|
+
|
|
85
|
+
You can pass an existing Hono app instance to add custom middleware:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { Hono } from 'hono'
|
|
89
|
+
import { logger } from 'hono/logger'
|
|
90
|
+
import AdminJS from 'adminjs'
|
|
91
|
+
import { buildRouter } from '@ahmedbaset/adminjs-hono'
|
|
92
|
+
|
|
93
|
+
const admin = new AdminJS({
|
|
94
|
+
databases: [],
|
|
95
|
+
rootPath: '/admin',
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Create a Hono app with custom middleware
|
|
99
|
+
const adminApp = new Hono()
|
|
100
|
+
adminApp.use('*', logger())
|
|
101
|
+
|
|
102
|
+
// Pass it to buildRouter
|
|
103
|
+
const adminRouter = buildRouter(admin, adminApp)
|
|
104
|
+
|
|
105
|
+
const app = new Hono()
|
|
106
|
+
app.route('/admin', adminRouter)
|
|
107
|
+
|
|
108
|
+
export default app
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API
|
|
112
|
+
|
|
113
|
+
### `buildRouter(admin, predefinedApp?, uploadOptions?)`
|
|
114
|
+
|
|
115
|
+
Creates a Hono app with AdminJS routes (no authentication).
|
|
116
|
+
|
|
117
|
+
**Parameters:**
|
|
118
|
+
- `admin` (AdminJS): AdminJS instance
|
|
119
|
+
- `predefinedApp` (Hono, optional): Existing Hono app to use
|
|
120
|
+
- `uploadOptions` (UploadOptions, optional): File upload configuration
|
|
121
|
+
|
|
122
|
+
**Returns:** Hono app with AdminJS routes
|
|
123
|
+
|
|
124
|
+
### `buildAuthenticatedRouter(admin, auth, predefinedApp?, sessionOptions?, uploadOptions?)`
|
|
125
|
+
|
|
126
|
+
Creates a Hono app with AdminJS routes protected by session authentication.
|
|
127
|
+
|
|
128
|
+
**Parameters:**
|
|
129
|
+
- `admin` (AdminJS): AdminJS instance
|
|
130
|
+
- `auth` (AuthenticationOptions): Authentication configuration
|
|
131
|
+
- `predefinedApp` (Hono, optional): Existing Hono app to use
|
|
132
|
+
- `sessionOptions` (SessionOptions, optional): Session cookie configuration
|
|
133
|
+
- `uploadOptions` (UploadOptions, optional): File upload configuration
|
|
134
|
+
|
|
135
|
+
**Returns:** Hono app with authenticated AdminJS routes
|
|
136
|
+
|
|
137
|
+
## Configuration Options
|
|
138
|
+
|
|
139
|
+
### AuthenticationOptions
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
{
|
|
143
|
+
cookiePassword: string // Required: Secret for session cookies
|
|
144
|
+
cookieName?: string // Optional: Cookie name (default: 'adminjs')
|
|
145
|
+
authenticate?: (email, password, context?) => Promise<CurrentAdmin | null>
|
|
146
|
+
maxRetries?: number | { count: number, duration: number }
|
|
147
|
+
provider?: BaseAuthProvider // Alternative to authenticate function
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### SessionOptions
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
{
|
|
155
|
+
maxAge?: number // Session duration in seconds (default: 86400)
|
|
156
|
+
httpOnly?: boolean // HttpOnly flag (default: true)
|
|
157
|
+
secure?: boolean // Secure flag (default: false)
|
|
158
|
+
sameSite?: 'Strict' | 'Lax' | 'None' // SameSite policy (default: 'Lax')
|
|
159
|
+
domain?: string // Cookie domain
|
|
160
|
+
path?: string // Cookie path (default: '/')
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### UploadOptions
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
{
|
|
168
|
+
uploadDir?: string // Upload directory path
|
|
169
|
+
maxFileSize?: number // Maximum file size in bytes
|
|
170
|
+
maxFieldsSize?: number // Maximum total fields size in bytes
|
|
171
|
+
maxFields?: number // Maximum number of fields
|
|
172
|
+
keepExtensions?: boolean // Keep file extensions
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Authentication Providers
|
|
177
|
+
|
|
178
|
+
You can use AdminJS authentication providers instead of the `authenticate` function:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { buildAuthenticatedRouter } from '@ahmedbaset/adminjs-hono'
|
|
182
|
+
import { MyAuthProvider } from './my-auth-provider'
|
|
183
|
+
|
|
184
|
+
const adminRouter = buildAuthenticatedRouter(
|
|
185
|
+
admin,
|
|
186
|
+
{
|
|
187
|
+
provider: new MyAuthProvider(),
|
|
188
|
+
cookiePassword: 'secret',
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Differences from Express Adapter
|
|
194
|
+
|
|
195
|
+
The Hono adapter maintains API compatibility with the Express adapter, but there are some differences:
|
|
196
|
+
|
|
197
|
+
1. **Runtime Support**: Works on all JavaScript runtimes, not just Node.js
|
|
198
|
+
2. **File Uploads**: Uses Web Standards FormData API instead of express-formidable
|
|
199
|
+
3. **Session Storage**: Uses in-memory storage by default (suitable for single-instance deployments)
|
|
200
|
+
4. **Middleware**: Uses Hono middleware instead of Express middleware
|
|
201
|
+
|
|
202
|
+
## Debugging
|
|
203
|
+
|
|
204
|
+
Set the `ADMINJS_HONO_DEBUG` environment variable to enable debug logging:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
ADMINJS_HONO_DEBUG=true node server.js
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Examples
|
|
211
|
+
|
|
212
|
+
See the [examples](./examples) directory for complete working examples:
|
|
213
|
+
|
|
214
|
+
- [simple.ts](./examples/simple.ts) - Basic usage without authentication
|
|
215
|
+
- [auth.ts](./examples/auth.ts) - Usage with authentication
|
|
216
|
+
|
|
217
|
+
## Troubleshooting
|
|
218
|
+
|
|
219
|
+
### Session not persisting
|
|
220
|
+
|
|
221
|
+
Make sure you're using the same `cookieName` across requests and that cookies are enabled in your browser. In production, set `secure: true` in session options when using HTTPS.
|
|
222
|
+
|
|
223
|
+
### File uploads not working
|
|
224
|
+
|
|
225
|
+
Ensure the `uploadDir` exists and is writable. The adapter uses Web Standards FormData API, which may have different behavior across runtimes.
|
|
226
|
+
|
|
227
|
+
### Assets not loading
|
|
228
|
+
|
|
229
|
+
Check that the AdminJS `rootPath` matches the path where you mount the router. For example, if `rootPath: '/admin'`, mount with `app.route('/admin', adminRouter)`.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
|
234
|
+
|
|
235
|
+
## Links
|
|
236
|
+
|
|
237
|
+
- [AdminJS Documentation](https://docs.adminjs.co/)
|
|
238
|
+
- [Hono Documentation](https://hono.dev/)
|
|
239
|
+
- [GitHub Repository](https://github.com/SoftwareBrothers/adminjs-hono)
|
package/examples/auth.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example of AdminJS with Hono and authentication
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to integrate AdminJS into a Hono application
|
|
5
|
+
* with session-based authentication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Hono } from 'hono'
|
|
9
|
+
import { serve } from '@hono/node-server'
|
|
10
|
+
import AdminJS from 'adminjs'
|
|
11
|
+
import { buildAuthenticatedRouter } from '../src/index.js'
|
|
12
|
+
|
|
13
|
+
// Mock admin user for demonstration
|
|
14
|
+
const ADMIN = {
|
|
15
|
+
email: 'admin@example.com',
|
|
16
|
+
password: 'password',
|
|
17
|
+
name: 'Admin User',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create AdminJS instance
|
|
21
|
+
const admin = new AdminJS({
|
|
22
|
+
databases: [],
|
|
23
|
+
rootPath: '/admin',
|
|
24
|
+
resources: [],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Build authenticated AdminJS router
|
|
28
|
+
const adminRouter = buildAuthenticatedRouter(
|
|
29
|
+
admin,
|
|
30
|
+
{
|
|
31
|
+
// Authentication function
|
|
32
|
+
authenticate: async (email, password) => {
|
|
33
|
+
// In production, verify against database with hashed passwords
|
|
34
|
+
if (email === ADMIN.email && password === ADMIN.password) {
|
|
35
|
+
return {
|
|
36
|
+
email: ADMIN.email,
|
|
37
|
+
name: ADMIN.name,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null
|
|
41
|
+
},
|
|
42
|
+
// Session cookie configuration
|
|
43
|
+
cookiePassword: 'some-secret-password-at-least-32-characters-long',
|
|
44
|
+
cookieName: 'adminjs',
|
|
45
|
+
},
|
|
46
|
+
undefined, // No predefined app
|
|
47
|
+
{
|
|
48
|
+
// Session options
|
|
49
|
+
maxAge: 86400, // 24 hours
|
|
50
|
+
httpOnly: true,
|
|
51
|
+
secure: process.env.NODE_ENV === 'production',
|
|
52
|
+
sameSite: 'Lax',
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Create main Hono app
|
|
57
|
+
const app = new Hono()
|
|
58
|
+
|
|
59
|
+
// Mount AdminJS router
|
|
60
|
+
app.route('/admin', adminRouter)
|
|
61
|
+
|
|
62
|
+
// Add a simple home route
|
|
63
|
+
app.get('/', (c) => {
|
|
64
|
+
return c.text('AdminJS is running at /admin (login required)')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Start server
|
|
68
|
+
const port = 3000
|
|
69
|
+
console.log(`Server is running on http://localhost:${port}`)
|
|
70
|
+
console.log(`AdminJS is available at http://localhost:${port}/admin`)
|
|
71
|
+
console.log(`Login with: ${ADMIN.email} / ${ADMIN.password}`)
|
|
72
|
+
|
|
73
|
+
serve({
|
|
74
|
+
fetch: app.fetch,
|
|
75
|
+
port,
|
|
76
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple example of AdminJS with Hono (no authentication)
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to integrate AdminJS into a Hono application
|
|
5
|
+
* without authentication. Suitable for development environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Hono } from 'hono'
|
|
9
|
+
import { serve } from '@hono/node-server'
|
|
10
|
+
import AdminJS from 'adminjs'
|
|
11
|
+
import { buildRouter } from '../src/index.js'
|
|
12
|
+
|
|
13
|
+
// Create AdminJS instance
|
|
14
|
+
const admin = new AdminJS({
|
|
15
|
+
databases: [],
|
|
16
|
+
rootPath: '/admin',
|
|
17
|
+
resources: [],
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Build AdminJS router
|
|
21
|
+
const adminRouter = buildRouter(admin)
|
|
22
|
+
|
|
23
|
+
// Create main Hono app
|
|
24
|
+
const app = new Hono()
|
|
25
|
+
|
|
26
|
+
// Mount AdminJS router
|
|
27
|
+
app.route('/admin', adminRouter)
|
|
28
|
+
|
|
29
|
+
// Add a simple home route
|
|
30
|
+
app.get('/', (c) => {
|
|
31
|
+
return c.text('AdminJS is running at /admin')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Start server
|
|
35
|
+
const port = 3000
|
|
36
|
+
console.log(`Server is running on http://localhost:${port}`)
|
|
37
|
+
console.log(`AdminJS is available at http://localhost:${port}/admin`)
|
|
38
|
+
|
|
39
|
+
serve({
|
|
40
|
+
fetch: app.fetch,
|
|
41
|
+
port,
|
|
42
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type AdminJS from 'adminjs';
|
|
2
|
+
import type { Hono } from 'hono';
|
|
3
|
+
import type { AuthenticationOptions } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Registers login routes with the Hono app
|
|
6
|
+
* @param app - Hono app instance
|
|
7
|
+
* @param admin - AdminJS instance
|
|
8
|
+
* @param auth - Authentication options
|
|
9
|
+
*/
|
|
10
|
+
export declare function withLogin(app: Hono, admin: AdminJS, auth: AuthenticationOptions): void;
|
|
11
|
+
//# sourceMappingURL=login.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.handler.d.ts","sourceRoot":"","sources":["../../src/authentication/login.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,MAAM,CAAA;AACzC,OAAO,KAAK,EAGV,qBAAqB,EAEtB,MAAM,aAAa,CAAA;AA8EpB;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,qBAAqB,GAC1B,IAAI,CAiGN"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { INVALID_AUTH_CONFIG_ERROR, WrongArgumentError } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes the login path by removing the root path
|
|
4
|
+
* @param admin - AdminJS instance
|
|
5
|
+
* @returns Normalized login path
|
|
6
|
+
*/
|
|
7
|
+
function getLoginPath(admin) {
|
|
8
|
+
const { loginPath, rootPath } = admin.options;
|
|
9
|
+
const normalizedLoginPath = loginPath.replace(rootPath, '');
|
|
10
|
+
return normalizedLoginPath.startsWith('/')
|
|
11
|
+
? normalizedLoginPath
|
|
12
|
+
: `/${normalizedLoginPath}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Manages login retry attempts per IP address
|
|
16
|
+
*/
|
|
17
|
+
class Retry {
|
|
18
|
+
static retriesContainer = new Map();
|
|
19
|
+
lastRetry;
|
|
20
|
+
retriesCount = 0;
|
|
21
|
+
constructor(ip) {
|
|
22
|
+
const existing = Retry.retriesContainer.get(ip);
|
|
23
|
+
if (existing) {
|
|
24
|
+
return existing;
|
|
25
|
+
}
|
|
26
|
+
Retry.retriesContainer.set(ip, this);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a login attempt is allowed based on retry limits
|
|
30
|
+
* @param maxRetries - Maximum retry configuration
|
|
31
|
+
* @returns true if login is allowed, false otherwise
|
|
32
|
+
*/
|
|
33
|
+
canLogin(maxRetries) {
|
|
34
|
+
if (maxRetries === undefined) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Convert number to AuthenticationMaxRetriesOptions
|
|
38
|
+
let retryConfig;
|
|
39
|
+
if (typeof maxRetries === 'number') {
|
|
40
|
+
retryConfig = {
|
|
41
|
+
count: maxRetries,
|
|
42
|
+
duration: 60, // per minute
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
retryConfig = maxRetries;
|
|
47
|
+
}
|
|
48
|
+
if (retryConfig.count <= 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
// Check if duration has passed since last retry
|
|
52
|
+
if (!this.lastRetry ||
|
|
53
|
+
new Date().getTime() - this.lastRetry.getTime() >
|
|
54
|
+
retryConfig.duration * 1000) {
|
|
55
|
+
// Reset counter
|
|
56
|
+
this.lastRetry = new Date();
|
|
57
|
+
this.retriesCount = 1;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Increment counter
|
|
62
|
+
this.lastRetry = new Date();
|
|
63
|
+
this.retriesCount++;
|
|
64
|
+
return this.retriesCount <= retryConfig.count;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Registers login routes with the Hono app
|
|
70
|
+
* @param app - Hono app instance
|
|
71
|
+
* @param admin - AdminJS instance
|
|
72
|
+
* @param auth - Authentication options
|
|
73
|
+
*/
|
|
74
|
+
export function withLogin(app, admin, auth) {
|
|
75
|
+
const { rootPath } = admin.options;
|
|
76
|
+
const loginPath = getLoginPath(admin);
|
|
77
|
+
const { provider } = auth;
|
|
78
|
+
const providerProps = provider?.getUiProps?.() ?? {};
|
|
79
|
+
// GET /login - Render login page
|
|
80
|
+
app.get(loginPath, async (c) => {
|
|
81
|
+
const baseProps = {
|
|
82
|
+
action: admin.options.loginPath,
|
|
83
|
+
errorMessage: null,
|
|
84
|
+
};
|
|
85
|
+
const login = await admin.renderLogin({
|
|
86
|
+
...baseProps,
|
|
87
|
+
...providerProps,
|
|
88
|
+
});
|
|
89
|
+
return c.html(login);
|
|
90
|
+
});
|
|
91
|
+
// POST /login - Handle login submission
|
|
92
|
+
app.post(loginPath, async (c) => {
|
|
93
|
+
// Get client IP for retry tracking
|
|
94
|
+
const ip = c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown';
|
|
95
|
+
// Check retry limits
|
|
96
|
+
if (!new Retry(ip).canLogin(auth.maxRetries)) {
|
|
97
|
+
const login = await admin.renderLogin({
|
|
98
|
+
action: admin.options.loginPath,
|
|
99
|
+
errorMessage: 'tooManyRequests',
|
|
100
|
+
...providerProps,
|
|
101
|
+
});
|
|
102
|
+
return c.html(login);
|
|
103
|
+
}
|
|
104
|
+
const context = { req: c, res: c };
|
|
105
|
+
let adminUser;
|
|
106
|
+
try {
|
|
107
|
+
if (provider) {
|
|
108
|
+
// Use authentication provider
|
|
109
|
+
const fields = c.get('fields') || {};
|
|
110
|
+
adminUser = await provider.handleLogin({
|
|
111
|
+
headers: Object.fromEntries(c.req.raw.headers.entries()),
|
|
112
|
+
query: c.req.query(),
|
|
113
|
+
params: c.req.param(),
|
|
114
|
+
data: fields,
|
|
115
|
+
}, context);
|
|
116
|
+
}
|
|
117
|
+
else if (auth.authenticate) {
|
|
118
|
+
// Use authenticate function
|
|
119
|
+
const fields = c.get('fields') || {};
|
|
120
|
+
const { email, password } = fields;
|
|
121
|
+
adminUser = await auth.authenticate(email, password, context);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw new WrongArgumentError(INVALID_AUTH_CONFIG_ERROR);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const errorMessage = error.message || error.error || 'invalidCredentials';
|
|
129
|
+
const loginPage = await admin.renderLogin({
|
|
130
|
+
action: admin.options.loginPath,
|
|
131
|
+
errorMessage,
|
|
132
|
+
...providerProps,
|
|
133
|
+
});
|
|
134
|
+
return c.html(loginPage, 400);
|
|
135
|
+
}
|
|
136
|
+
if (adminUser) {
|
|
137
|
+
// Store user in session
|
|
138
|
+
const session = c.get('session');
|
|
139
|
+
session.adminUser = adminUser;
|
|
140
|
+
// Redirect to original path or root
|
|
141
|
+
const redirectTo = session.redirectTo || rootPath;
|
|
142
|
+
delete session.redirectTo;
|
|
143
|
+
return c.redirect(redirectTo, 302);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Invalid credentials
|
|
147
|
+
const login = await admin.renderLogin({
|
|
148
|
+
action: admin.options.loginPath,
|
|
149
|
+
errorMessage: 'invalidCredentials',
|
|
150
|
+
...providerProps,
|
|
151
|
+
});
|
|
152
|
+
return c.html(login);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type AdminJS from 'adminjs';
|
|
2
|
+
import type { Hono } from 'hono';
|
|
3
|
+
import type { AuthenticationOptions } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Registers logout route with the Hono app
|
|
6
|
+
* @param app - Hono app instance
|
|
7
|
+
* @param admin - AdminJS instance
|
|
8
|
+
* @param auth - Authentication options
|
|
9
|
+
*/
|
|
10
|
+
export declare function withLogout(app: Hono, admin: AdminJS, auth: AuthenticationOptions): void;
|
|
11
|
+
//# sourceMappingURL=logout.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.handler.d.ts","sourceRoot":"","sources":["../../src/authentication/logout.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,MAAM,CAAA;AAEzC,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,aAAa,CAAA;AAgBvE;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,qBAAqB,GAC1B,IAAI,CAgCN"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getCookie } from 'hono/cookie';
|
|
2
|
+
import { destroySession } from '../session.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes the logout path by removing the root path
|
|
5
|
+
* @param admin - AdminJS instance
|
|
6
|
+
* @returns Normalized logout path
|
|
7
|
+
*/
|
|
8
|
+
function getLogoutPath(admin) {
|
|
9
|
+
const { logoutPath, rootPath } = admin.options;
|
|
10
|
+
const normalizedLogoutPath = logoutPath.replace(rootPath, '');
|
|
11
|
+
return normalizedLogoutPath.startsWith('/')
|
|
12
|
+
? normalizedLogoutPath
|
|
13
|
+
: `/${normalizedLogoutPath}`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Registers logout route with the Hono app
|
|
17
|
+
* @param app - Hono app instance
|
|
18
|
+
* @param admin - AdminJS instance
|
|
19
|
+
* @param auth - Authentication options
|
|
20
|
+
*/
|
|
21
|
+
export function withLogout(app, admin, auth) {
|
|
22
|
+
const logoutPath = getLogoutPath(admin);
|
|
23
|
+
const { provider } = auth;
|
|
24
|
+
app.get(logoutPath, async (c) => {
|
|
25
|
+
// Call provider's handleLogout if available
|
|
26
|
+
if (provider) {
|
|
27
|
+
try {
|
|
28
|
+
await provider.handleLogout({ req: c, res: c });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
// Fail silently and continue with logout
|
|
32
|
+
console.error('Provider logout error:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Get session ID and destroy session
|
|
36
|
+
const cookieName = auth.cookieName || 'adminjs';
|
|
37
|
+
const sessionId = getCookie(c, cookieName);
|
|
38
|
+
if (sessionId) {
|
|
39
|
+
destroySession(sessionId);
|
|
40
|
+
}
|
|
41
|
+
// Clear session from context
|
|
42
|
+
const session = c.get('session');
|
|
43
|
+
if (session) {
|
|
44
|
+
delete session.adminUser;
|
|
45
|
+
delete session.redirectTo;
|
|
46
|
+
}
|
|
47
|
+
// Redirect to login page
|
|
48
|
+
return c.redirect(admin.options.loginPath);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type AdminJS from 'adminjs';
|
|
2
|
+
import type { Hono } from 'hono';
|
|
3
|
+
/**
|
|
4
|
+
* Registers middleware to protect routes that require authentication
|
|
5
|
+
* Redirects unauthenticated requests to the login page
|
|
6
|
+
*
|
|
7
|
+
* @param app - Hono app instance
|
|
8
|
+
* @param admin - AdminJS instance
|
|
9
|
+
*/
|
|
10
|
+
export declare function withProtectedRoutesHandler(app: Hono, admin: AdminJS): void;
|
|
11
|
+
//# sourceMappingURL=protected-routes.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protected-routes.handler.d.ts","sourceRoot":"","sources":["../../src/authentication/protected-routes.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,KAAK,EAAE,IAAI,EAA8B,MAAM,MAAM,CAAA;AAG5D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,GACb,IAAI,CAuBN"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers middleware to protect routes that require authentication
|
|
3
|
+
* Redirects unauthenticated requests to the login page
|
|
4
|
+
*
|
|
5
|
+
* @param app - Hono app instance
|
|
6
|
+
* @param admin - AdminJS instance
|
|
7
|
+
*/
|
|
8
|
+
export function withProtectedRoutesHandler(app, admin) {
|
|
9
|
+
const { loginPath } = admin.options;
|
|
10
|
+
const authorizedRoutesMiddleware = async (c, next) => {
|
|
11
|
+
const session = c.get('session');
|
|
12
|
+
// Check if user is authenticated
|
|
13
|
+
if (!session || !session.adminUser) {
|
|
14
|
+
// Store the original path for redirect after login
|
|
15
|
+
session.redirectTo = c.req.path;
|
|
16
|
+
// Redirect to login page
|
|
17
|
+
return c.redirect(loginPath);
|
|
18
|
+
}
|
|
19
|
+
// User is authenticated, proceed
|
|
20
|
+
return next();
|
|
21
|
+
};
|
|
22
|
+
// Apply middleware to all routes
|
|
23
|
+
// Note: This should be registered after login/logout routes
|
|
24
|
+
// so they remain accessible without authentication
|
|
25
|
+
app.use('*', authorizedRoutesMiddleware);
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type AdminJS from 'adminjs';
|
|
2
|
+
import type { Hono } from 'hono';
|
|
3
|
+
import type { AuthenticationOptions } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Registers token refresh route with the Hono app
|
|
6
|
+
* Delegates to authentication provider's refresh method if available
|
|
7
|
+
*
|
|
8
|
+
* @param app - Hono app instance
|
|
9
|
+
* @param admin - AdminJS instance
|
|
10
|
+
* @param auth - Authentication options
|
|
11
|
+
*/
|
|
12
|
+
export declare function withRefresh(app: Hono, admin: AdminJS, auth: AuthenticationOptions): void;
|
|
13
|
+
//# sourceMappingURL=refresh.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.handler.d.ts","sourceRoot":"","sources":["../../src/authentication/refresh.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,MAAM,CAAA;AACzC,OAAO,KAAK,EAAE,qBAAqB,EAAiB,MAAM,aAAa,CAAA;AAEvE;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,qBAAqB,GAC1B,IAAI,CA0CN"}
|