@basicbenframework/core 0.1.0 → 0.1.5
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/.github/workflows/publish.yml +4 -5
- package/LICENSE +9 -0
- package/README.md +5 -5
- package/create-basicben-app/index.js +11 -9
- package/create-basicben-app/package.json +4 -4
- package/create-basicben-app/template/README.md +2 -2
- package/create-basicben-app/template/gitignore +31 -0
- package/create-basicben-app/template/src/client/pages/GettingStarted.jsx +2 -2
- package/create-basicben-app/template/src/client/pages/Home.jsx +2 -2
- package/package.json +11 -11
- package/scripts/publish.sh +125 -0
- package/my-test-app/.env.example +0 -24
- package/my-test-app/README.md +0 -59
- package/my-test-app/basicben.config.js +0 -33
- package/my-test-app/database.sqlite-shm +0 -0
- package/my-test-app/database.sqlite-wal +0 -0
- package/my-test-app/index.html +0 -54
- package/my-test-app/migrations/001_create_users.js +0 -15
- package/my-test-app/migrations/002_create_posts.js +0 -18
- package/my-test-app/package-lock.json +0 -2160
- package/my-test-app/package.json +0 -29
- package/my-test-app/public/.gitkeep +0 -0
- package/my-test-app/seeds/01_users.js +0 -29
- package/my-test-app/seeds/02_posts.js +0 -43
- package/my-test-app/src/client/components/Alert.jsx +0 -11
- package/my-test-app/src/client/components/Avatar.jsx +0 -11
- package/my-test-app/src/client/components/BackLink.jsx +0 -10
- package/my-test-app/src/client/components/Button.jsx +0 -19
- package/my-test-app/src/client/components/Card.jsx +0 -10
- package/my-test-app/src/client/components/Empty.jsx +0 -6
- package/my-test-app/src/client/components/Input.jsx +0 -12
- package/my-test-app/src/client/components/Loading.jsx +0 -6
- package/my-test-app/src/client/components/Logo.jsx +0 -40
- package/my-test-app/src/client/components/Nav/DarkModeToggle.jsx +0 -23
- package/my-test-app/src/client/components/Nav/DesktopNav.jsx +0 -32
- package/my-test-app/src/client/components/Nav/MobileNav.jsx +0 -107
- package/my-test-app/src/client/components/NavLink.jsx +0 -10
- package/my-test-app/src/client/components/PageHeader.jsx +0 -8
- package/my-test-app/src/client/components/PostCard.jsx +0 -19
- package/my-test-app/src/client/components/Textarea.jsx +0 -12
- package/my-test-app/src/client/components/ThemeContext.jsx +0 -5
- package/my-test-app/src/client/contexts/AppContext.jsx +0 -13
- package/my-test-app/src/client/contexts/ToastContext.jsx +0 -94
- package/my-test-app/src/client/layouts/AppLayout.jsx +0 -60
- package/my-test-app/src/client/layouts/AuthLayout.jsx +0 -33
- package/my-test-app/src/client/layouts/DocsLayout.jsx +0 -60
- package/my-test-app/src/client/layouts/RootLayout.jsx +0 -25
- package/my-test-app/src/client/pages/Auth.jsx +0 -55
- package/my-test-app/src/client/pages/Authentication.jsx +0 -236
- package/my-test-app/src/client/pages/Database.jsx +0 -426
- package/my-test-app/src/client/pages/Feed.jsx +0 -34
- package/my-test-app/src/client/pages/FeedPost.jsx +0 -37
- package/my-test-app/src/client/pages/GettingStarted.jsx +0 -136
- package/my-test-app/src/client/pages/Home.jsx +0 -206
- package/my-test-app/src/client/pages/PostForm.jsx +0 -69
- package/my-test-app/src/client/pages/Posts.jsx +0 -59
- package/my-test-app/src/client/pages/Profile.jsx +0 -68
- package/my-test-app/src/client/pages/Routing.jsx +0 -207
- package/my-test-app/src/client/pages/Testing.jsx +0 -251
- package/my-test-app/src/client/pages/Validation.jsx +0 -210
- package/my-test-app/src/controllers/AuthController.js +0 -81
- package/my-test-app/src/controllers/HomeController.js +0 -17
- package/my-test-app/src/controllers/PostController.js +0 -86
- package/my-test-app/src/controllers/ProfileController.js +0 -66
- package/my-test-app/src/helpers/api.js +0 -24
- package/my-test-app/src/main.jsx +0 -9
- package/my-test-app/src/middleware/auth.js +0 -16
- package/my-test-app/src/models/Post.js +0 -63
- package/my-test-app/src/models/User.js +0 -42
- package/my-test-app/src/routes/App.jsx +0 -38
- package/my-test-app/src/routes/api/auth.js +0 -7
- package/my-test-app/src/routes/api/posts.js +0 -15
- package/my-test-app/src/routes/api/profile.js +0 -8
- package/my-test-app/src/server/index.js +0 -16
- package/my-test-app/vite.config.js +0 -18
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { useNavigate } from '@basicbenframework/core/client'
|
|
2
|
-
import { useTheme } from '../components/ThemeContext'
|
|
3
|
-
import { RootLayout } from './RootLayout'
|
|
4
|
-
import { DarkModeToggle } from '../components/Nav/DarkModeToggle'
|
|
5
|
-
import { Logo } from '../components/Logo'
|
|
6
|
-
|
|
7
|
-
function AuthLayoutInner({ children }) {
|
|
8
|
-
const { t, dark, setDark } = useTheme()
|
|
9
|
-
const navigate = useNavigate()
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<div className="max-w-3xl mx-auto px-6">
|
|
13
|
-
<nav className={`flex items-center justify-between h-14 border-b ${t.border}`}>
|
|
14
|
-
<button onClick={() => navigate('/')} className="flex items-center gap-2 font-semibold hover:opacity-70 transition">
|
|
15
|
-
<Logo className="w-6 h-6" />
|
|
16
|
-
<span>BasicBen</span>
|
|
17
|
-
</button>
|
|
18
|
-
<DarkModeToggle dark={dark} setDark={setDark} />
|
|
19
|
-
</nav>
|
|
20
|
-
<main className="py-8 flex items-center justify-center min-h-[calc(100vh-3.5rem)]">
|
|
21
|
-
{children}
|
|
22
|
-
</main>
|
|
23
|
-
</div>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function AuthLayout({ children }) {
|
|
28
|
-
return (
|
|
29
|
-
<RootLayout>
|
|
30
|
-
<AuthLayoutInner>{children}</AuthLayoutInner>
|
|
31
|
-
</RootLayout>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { useNavigate, usePath } from '@basicbenframework/core/client'
|
|
2
|
-
import { useTheme } from '../components/ThemeContext'
|
|
3
|
-
import { AppLayout } from './AppLayout'
|
|
4
|
-
|
|
5
|
-
function SidebarLink({ href, active, children }) {
|
|
6
|
-
const { t } = useTheme()
|
|
7
|
-
const navigate = useNavigate()
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<button
|
|
11
|
-
onClick={() => navigate(href)}
|
|
12
|
-
className={`block w-full text-left px-3 py-2 rounded-lg text-sm transition ${
|
|
13
|
-
active ? `${t.card} font-medium` : `${t.muted} hover:opacity-70`
|
|
14
|
-
}`}
|
|
15
|
-
>
|
|
16
|
-
{children}
|
|
17
|
-
</button>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function DocsSidebar({ children }) {
|
|
22
|
-
const { t } = useTheme()
|
|
23
|
-
const path = usePath()
|
|
24
|
-
|
|
25
|
-
const docLinks = [
|
|
26
|
-
{ href: '/docs', label: 'Getting Started' },
|
|
27
|
-
{ href: '/docs/routing', label: 'Routing' },
|
|
28
|
-
{ href: '/docs/database', label: 'Database' },
|
|
29
|
-
{ href: '/docs/authentication', label: 'Authentication' },
|
|
30
|
-
{ href: '/docs/validation', label: 'Validation' },
|
|
31
|
-
{ href: '/docs/testing', label: 'Testing' },
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="flex gap-8">
|
|
36
|
-
<aside className="hidden md:block w-48 flex-shrink-0">
|
|
37
|
-
<nav className="sticky top-20 space-y-1">
|
|
38
|
-
{docLinks.map(link => (
|
|
39
|
-
<SidebarLink
|
|
40
|
-
key={link.href}
|
|
41
|
-
href={link.href}
|
|
42
|
-
active={path === link.href}
|
|
43
|
-
>
|
|
44
|
-
{link.label}
|
|
45
|
-
</SidebarLink>
|
|
46
|
-
))}
|
|
47
|
-
</nav>
|
|
48
|
-
</aside>
|
|
49
|
-
<div className="flex-1 min-w-0">{children}</div>
|
|
50
|
-
</div>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function DocsLayout({ children }) {
|
|
55
|
-
return (
|
|
56
|
-
<AppLayout>
|
|
57
|
-
<DocsSidebar>{children}</DocsSidebar>
|
|
58
|
-
</AppLayout>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { ThemeContext } from '../components/ThemeContext'
|
|
3
|
-
import { ToastProvider } from '../contexts/ToastContext'
|
|
4
|
-
|
|
5
|
-
export function RootLayout({ children }) {
|
|
6
|
-
const [dark, setDark] = useState(true)
|
|
7
|
-
|
|
8
|
-
const t = dark
|
|
9
|
-
? { bg: 'bg-black', text: 'text-white', muted: 'text-white/50', subtle: 'text-white/30', border: 'border-white/10', card: 'bg-white/5', btn: 'bg-white text-black', btnHover: 'hover:bg-white/90', btnSecondary: 'bg-white/10 hover:bg-white/20' }
|
|
10
|
-
: { bg: 'bg-white', text: 'text-black', muted: 'text-black/50', subtle: 'text-black/30', border: 'border-black/10', card: 'bg-black/5', btn: 'bg-black text-white', btnHover: 'hover:bg-black/90', btnSecondary: 'bg-black/10 hover:bg-black/20' }
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<ThemeContext.Provider value={{ t, dark, setDark }}>
|
|
14
|
-
<ToastProvider>
|
|
15
|
-
<div className={`min-h-screen ${t.bg} ${t.text} transition-colors duration-300`}>
|
|
16
|
-
<div className="fixed inset-0 pointer-events-none overflow-hidden">
|
|
17
|
-
<div className={`absolute top-0 right-0 w-[500px] h-[500px] rounded-full blur-[150px] ${dark ? 'bg-purple-500/10' : 'bg-purple-500/5'}`} />
|
|
18
|
-
<div className={`absolute bottom-0 left-0 w-[500px] h-[500px] rounded-full blur-[150px] ${dark ? 'bg-blue-500/10' : 'bg-blue-500/5'}`} />
|
|
19
|
-
</div>
|
|
20
|
-
<div className="relative">{children}</div>
|
|
21
|
-
</div>
|
|
22
|
-
</ToastProvider>
|
|
23
|
-
</ThemeContext.Provider>
|
|
24
|
-
)
|
|
25
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { useAuth, useNavigate, usePath } from '@basicbenframework/core/client'
|
|
3
|
-
import { useTheme } from '../components/ThemeContext'
|
|
4
|
-
import { Input } from '../components/Input'
|
|
5
|
-
import { Button } from '../components/Button'
|
|
6
|
-
import { api } from '../../helpers/api'
|
|
7
|
-
import { useToast } from '../contexts/ToastContext'
|
|
8
|
-
|
|
9
|
-
export function Auth() {
|
|
10
|
-
const { setUser } = useAuth()
|
|
11
|
-
const navigate = useNavigate()
|
|
12
|
-
const path = usePath()
|
|
13
|
-
const { t } = useTheme()
|
|
14
|
-
const toast = useToast()
|
|
15
|
-
const [form, setForm] = useState({ name: '', email: '', password: '' })
|
|
16
|
-
const [loading, setLoading] = useState(false)
|
|
17
|
-
const isLogin = path === '/login'
|
|
18
|
-
|
|
19
|
-
const handleSubmit = async (e) => {
|
|
20
|
-
e.preventDefault()
|
|
21
|
-
setLoading(true)
|
|
22
|
-
try {
|
|
23
|
-
const endpoint = isLogin ? '/api/auth/login' : '/api/auth/register'
|
|
24
|
-
const data = await api(endpoint, {
|
|
25
|
-
method: 'POST',
|
|
26
|
-
body: JSON.stringify(isLogin ? { email: form.email, password: form.password } : form)
|
|
27
|
-
})
|
|
28
|
-
localStorage.setItem('token', data.token)
|
|
29
|
-
setUser(data.user)
|
|
30
|
-
toast.success(isLogin ? 'Welcome back!' : 'Account created!')
|
|
31
|
-
navigate('/')
|
|
32
|
-
} catch (err) {
|
|
33
|
-
toast.error(err.message)
|
|
34
|
-
} finally {
|
|
35
|
-
setLoading(false)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div className="max-w-xs mx-auto py-8">
|
|
41
|
-
<h1 className="text-2xl font-bold text-center mb-1">{isLogin ? 'Welcome back' : 'Create account'}</h1>
|
|
42
|
-
<p className={`text-sm ${t.muted} text-center mb-6`}>{isLogin ? 'Sign in to continue' : 'Get started for free'}</p>
|
|
43
|
-
<form onSubmit={handleSubmit} className="space-y-3 mt-4">
|
|
44
|
-
{!isLogin && <Input placeholder="Name" required value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />}
|
|
45
|
-
<Input type="email" placeholder="Email" required value={form.email} onChange={e => setForm({ ...form, email: e.target.value })} />
|
|
46
|
-
<Input type="password" placeholder="Password" required minLength={8} value={form.password} onChange={e => setForm({ ...form, password: e.target.value })} />
|
|
47
|
-
<Button type="submit" disabled={loading} className="w-full">{loading ? '...' : isLogin ? 'Sign in' : 'Create account'}</Button>
|
|
48
|
-
</form>
|
|
49
|
-
<p className={`text-xs ${t.muted} text-center mt-4`}>
|
|
50
|
-
{isLogin ? "Don't have an account? " : 'Have an account? '}
|
|
51
|
-
<button onClick={() => navigate(isLogin ? '/register' : '/login')} className="underline hover:no-underline">{isLogin ? 'Sign up' : 'Sign in'}</button>
|
|
52
|
-
</p>
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { useTheme } from '../components/ThemeContext'
|
|
2
|
-
import { Card } from '../components/Card'
|
|
3
|
-
import { PageHeader } from '../components/PageHeader'
|
|
4
|
-
import { AppLayout } from '../layouts/AppLayout'
|
|
5
|
-
import { DocsLayout } from '../layouts/DocsLayout'
|
|
6
|
-
|
|
7
|
-
export function Authentication() {
|
|
8
|
-
const { t } = useTheme()
|
|
9
|
-
|
|
10
|
-
const CodeBlock = ({ children, title }) => (
|
|
11
|
-
<div className="mt-4">
|
|
12
|
-
{title && <div className={`text-xs font-medium mb-2 ${t.muted}`}>{title}</div>}
|
|
13
|
-
<div className={`rounded-lg p-4 font-mono text-sm ${t.card} border ${t.border} overflow-x-auto`}>
|
|
14
|
-
<pre className={t.text}>{children}</pre>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div>
|
|
21
|
-
<PageHeader
|
|
22
|
-
title="Authentication"
|
|
23
|
-
subtitle="JWT auth, password hashing, and protected routes"
|
|
24
|
-
/>
|
|
25
|
-
|
|
26
|
-
<div className="space-y-6">
|
|
27
|
-
<Card>
|
|
28
|
-
<h2 className="text-lg font-semibold mb-2">Overview</h2>
|
|
29
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
30
|
-
BasicBen includes a complete JWT-based authentication system with password hashing, token generation, and middleware for protected routes.
|
|
31
|
-
</p>
|
|
32
|
-
|
|
33
|
-
<div className="grid gap-3 sm:grid-cols-3">
|
|
34
|
-
<div className={`rounded-lg p-3 ${t.card} border ${t.border}`}>
|
|
35
|
-
<div className="font-semibold text-sm">JWT Tokens</div>
|
|
36
|
-
<p className={`text-xs mt-1 ${t.muted}`}>Stateless authentication</p>
|
|
37
|
-
</div>
|
|
38
|
-
<div className={`rounded-lg p-3 ${t.card} border ${t.border}`}>
|
|
39
|
-
<div className="font-semibold text-sm">Bcrypt Hashing</div>
|
|
40
|
-
<p className={`text-xs mt-1 ${t.muted}`}>Secure password storage</p>
|
|
41
|
-
</div>
|
|
42
|
-
<div className={`rounded-lg p-3 ${t.card} border ${t.border}`}>
|
|
43
|
-
<div className="font-semibold text-sm">Auth Middleware</div>
|
|
44
|
-
<p className={`text-xs mt-1 ${t.muted}`}>Protect your routes</p>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</Card>
|
|
48
|
-
|
|
49
|
-
<Card>
|
|
50
|
-
<h2 className="text-lg font-semibold mb-2">Password Hashing</h2>
|
|
51
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
52
|
-
Use bcrypt to securely hash and verify passwords.
|
|
53
|
-
</p>
|
|
54
|
-
|
|
55
|
-
<CodeBlock title="Hash and verify passwords">
|
|
56
|
-
{`import { hashPassword, verifyPassword } from 'basicben/auth'
|
|
57
|
-
|
|
58
|
-
// Hash a password (for registration)
|
|
59
|
-
const hashedPassword = await hashPassword('user-password')
|
|
60
|
-
|
|
61
|
-
// Verify a password (for login)
|
|
62
|
-
const isValid = await verifyPassword('user-password', hashedPassword)
|
|
63
|
-
// Returns true or false`}
|
|
64
|
-
</CodeBlock>
|
|
65
|
-
</Card>
|
|
66
|
-
|
|
67
|
-
<Card>
|
|
68
|
-
<h2 className="text-lg font-semibold mb-2">JWT Tokens</h2>
|
|
69
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
70
|
-
Generate and verify JWT tokens for stateless authentication.
|
|
71
|
-
</p>
|
|
72
|
-
|
|
73
|
-
<CodeBlock title="Generate and verify tokens">
|
|
74
|
-
{`import { generateToken, verifyToken } from 'basicben/auth'
|
|
75
|
-
|
|
76
|
-
// Generate a token (after successful login)
|
|
77
|
-
const token = await generateToken({ userId: user.id })
|
|
78
|
-
|
|
79
|
-
// Verify a token (in middleware)
|
|
80
|
-
try {
|
|
81
|
-
const payload = await verifyToken(token)
|
|
82
|
-
console.log(payload.userId) // The user ID from the token
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// Token is invalid or expired
|
|
85
|
-
}`}
|
|
86
|
-
</CodeBlock>
|
|
87
|
-
|
|
88
|
-
<CodeBlock title="Configure token expiration in basicben.config.js">
|
|
89
|
-
{`export default {
|
|
90
|
-
auth: {
|
|
91
|
-
jwtSecret: process.env.JWT_SECRET,
|
|
92
|
-
jwtExpiresIn: '7d' // Token expires in 7 days
|
|
93
|
-
}
|
|
94
|
-
}`}
|
|
95
|
-
</CodeBlock>
|
|
96
|
-
</Card>
|
|
97
|
-
|
|
98
|
-
<Card>
|
|
99
|
-
<h2 className="text-lg font-semibold mb-2">Registration</h2>
|
|
100
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
101
|
-
Example registration endpoint that creates a user and returns a token.
|
|
102
|
-
</p>
|
|
103
|
-
|
|
104
|
-
<CodeBlock title="src/controllers/AuthController.js">
|
|
105
|
-
{`import { db } from 'basicben'
|
|
106
|
-
import { hashPassword, generateToken } from 'basicben/auth'
|
|
107
|
-
import { validate, rules } from 'basicben/validation'
|
|
108
|
-
|
|
109
|
-
export const AuthController = {
|
|
110
|
-
register: async (req, res) => {
|
|
111
|
-
// Validate input
|
|
112
|
-
const result = await validate(req.body, {
|
|
113
|
-
name: [rules.required, rules.minLength(2)],
|
|
114
|
-
email: [rules.required, rules.email, rules.unique('users')],
|
|
115
|
-
password: [rules.required, rules.minLength(8)]
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
if (!result.valid) {
|
|
119
|
-
return res.status(400).json({ errors: result.errors })
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Create user
|
|
123
|
-
const hashedPassword = await hashPassword(req.body.password)
|
|
124
|
-
|
|
125
|
-
const { lastInsertRowid } = await (await db.table('users')).insert({
|
|
126
|
-
name: req.body.name,
|
|
127
|
-
email: req.body.email,
|
|
128
|
-
password: hashedPassword
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// Get the created user
|
|
132
|
-
const user = await (await db.table('users')).find(lastInsertRowid)
|
|
133
|
-
|
|
134
|
-
// Generate token
|
|
135
|
-
const token = await generateToken({ userId: user.id })
|
|
136
|
-
|
|
137
|
-
return res.status(201).json({
|
|
138
|
-
user: { id: user.id, name: user.name, email: user.email },
|
|
139
|
-
token
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
}`}
|
|
143
|
-
</CodeBlock>
|
|
144
|
-
</Card>
|
|
145
|
-
|
|
146
|
-
<Card>
|
|
147
|
-
<h2 className="text-lg font-semibold mb-2">Login</h2>
|
|
148
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
149
|
-
Example login endpoint that verifies credentials and returns a token.
|
|
150
|
-
</p>
|
|
151
|
-
|
|
152
|
-
<CodeBlock title="Login handler">
|
|
153
|
-
{`import { verifyPassword, generateToken } from 'basicben/auth'
|
|
154
|
-
|
|
155
|
-
export const AuthController = {
|
|
156
|
-
login: async (req, res) => {
|
|
157
|
-
const { email, password } = req.body
|
|
158
|
-
|
|
159
|
-
// Find user by email
|
|
160
|
-
const user = await (await db.table('users'))
|
|
161
|
-
.where('email', email)
|
|
162
|
-
.first()
|
|
163
|
-
|
|
164
|
-
if (!user) {
|
|
165
|
-
return res.status(401).json({ error: 'Invalid credentials' })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Verify password
|
|
169
|
-
const valid = await verifyPassword(password, user.password)
|
|
170
|
-
|
|
171
|
-
if (!valid) {
|
|
172
|
-
return res.status(401).json({ error: 'Invalid credentials' })
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Generate token
|
|
176
|
-
const token = await generateToken({ userId: user.id })
|
|
177
|
-
|
|
178
|
-
return res.json({
|
|
179
|
-
user: { id: user.id, name: user.name, email: user.email },
|
|
180
|
-
token
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
}`}
|
|
184
|
-
</CodeBlock>
|
|
185
|
-
</Card>
|
|
186
|
-
|
|
187
|
-
<Card>
|
|
188
|
-
<h2 className="text-lg font-semibold mb-2">Auth Middleware</h2>
|
|
189
|
-
<p className={`text-sm ${t.muted} mb-4`}>
|
|
190
|
-
Protect routes by requiring a valid JWT token.
|
|
191
|
-
</p>
|
|
192
|
-
|
|
193
|
-
<CodeBlock title="src/middleware/auth.js">
|
|
194
|
-
{`import { verifyToken } from 'basicben/auth'
|
|
195
|
-
import { db } from 'basicben'
|
|
196
|
-
|
|
197
|
-
export const auth = async (req, res, next) => {
|
|
198
|
-
const header = req.headers.authorization
|
|
199
|
-
|
|
200
|
-
if (!header?.startsWith('Bearer ')) {
|
|
201
|
-
return res.status(401).json({ error: 'Unauthorized' })
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const token = header.slice(7)
|
|
206
|
-
const { userId } = await verifyToken(token)
|
|
207
|
-
|
|
208
|
-
req.user = await (await db.table('users')).find(userId)
|
|
209
|
-
|
|
210
|
-
if (!req.user) {
|
|
211
|
-
return res.status(401).json({ error: 'Unauthorized' })
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
next()
|
|
215
|
-
} catch {
|
|
216
|
-
return res.status(401).json({ error: 'Invalid token' })
|
|
217
|
-
}
|
|
218
|
-
}`}
|
|
219
|
-
</CodeBlock>
|
|
220
|
-
|
|
221
|
-
<CodeBlock title="Using auth middleware in routes">
|
|
222
|
-
{`import { auth } from '../middleware/auth.js'
|
|
223
|
-
|
|
224
|
-
export default [
|
|
225
|
-
// Public route
|
|
226
|
-
{ method: 'GET', path: '/api/posts', handler: PostController.index },
|
|
227
|
-
|
|
228
|
-
// Protected route - requires authentication
|
|
229
|
-
{ method: 'POST', path: '/api/posts', handler: PostController.store, middleware: [auth] },
|
|
230
|
-
]`}
|
|
231
|
-
</CodeBlock>
|
|
232
|
-
</Card>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
)
|
|
236
|
-
}
|