非常好, 我们可以在现有基础上添加用户认证功能, 使用 Google 和 GitHub 的 OAuth 2 认证。以下是实现步骤:

  1. 首先, 安装必要的依赖:
bun add @elysiajs/cookie @elysiajs/jwt oauth4webapi
  1. 在 Google Cloud Console 和 GitHub Developer settings 中创建 OAuth 应用, 获取客户端 ID 和密钥。

  2. 创建一个新的配置文件 config.ts:

// config.ts
export const config = {
  google: {
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    redirectUri: 'http://localhost:3000/auth/google/callback',
  },
  github: {
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    redirectUri: 'http://localhost:3000/auth/github/callback',
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'your-secret-key',
  },
}
  1. 创建一个认证处理模块 auth.ts:
// auth.ts
import { Elysia } from 'elysia'
import { cookie } from '@elysiajs/cookie'
import { jwt } from '@elysiajs/jwt'
import * as oauth from 'oauth4webapi'
import { config } from './config'
 
export const auth = (app: Elysia) =>
  app
    .use(cookie())
    .use(
      jwt({
        name: 'jwt',
        secret: config.jwt.secret,
      })
    )
    .get('/auth/google', async ({ set }) => {
      const client = new oauth.Client({
        clientId: config.google.clientId,
        clientSecret: config.google.clientSecret,
      })
      const authorizationUrl = await oauth.getAuthorizationUrl(client, {
        scope: ['openid', 'profile', 'email'],
        redirectUri: config.google.redirectUri,
      })
      set.redirect = authorizationUrl
    })
    .get('/auth/google/callback', async ({ query, jwt, setCookie, set }) => {
      const client = new oauth.Client({
        clientId: config.google.clientId,
        clientSecret: config.google.clientSecret,
      })
      const tokens = await oauth.getTokens(client, {
        code: query.code as string,
        redirectUri: config.google.redirectUri,
      })
      const userinfo = await oauth.getUserInfo(client, tokens)
      const token = await jwt.sign({
        id: userinfo.sub,
        email: userinfo.email,
        name: userinfo.name,
      })
      setCookie('auth', token, {
        httpOnly: true,
        maxAge: 7 * 86400,
      })
      set.redirect = '/'
    })
    .get('/auth/github', async ({ set }) => {
      const client = new oauth.Client({
        clientId: config.github.clientId,
        clientSecret: config.github.clientSecret,
      })
      const authorizationUrl = await oauth.getAuthorizationUrl(client, {
        scope: ['user:email'],
        redirectUri: config.github.redirectUri,
      })
      set.redirect = authorizationUrl
    })
    .get('/auth/github/callback', async ({ query, jwt, setCookie, set }) => {
      const client = new oauth.Client({
        clientId: config.github.clientId,
        clientSecret: config.github.clientSecret,
      })
      const tokens = await oauth.getTokens(client, {
        code: query.code as string,
        redirectUri: config.github.redirectUri,
      })
      const userinfo = await oauth.getUserInfo(client, tokens)
      const token = await jwt.sign({
        id: userinfo.id,
        email: userinfo.email,
        name: userinfo.name,
      })
      SetCookie ('auth', token, {
        HttpOnly: true,
        maxAge: 7 * 86400,
      })
      set.redirect = '/'
    })
    .get('/auth/logout', ({ removeCookie, set }) => {
      removeCookie('auth')
      set.redirect = '/'
    })
    .derive(async ({ cookie, jwt }) => {
      const authCookie = cookie.auth
      if (!authCookie) return { user: null }
      try {
        const user = await jwt.verify(authCookie)
        return { user }
      } catch {
        return { user: null }
      }
    })
  1. 修改 src/index.ts 文件, 集成认证功能:
import { Elysia } from 'elysia'
import { html } from '@elysiajs/html'
import { createClient } from '@libsql/client'
import { staticPlugin } from '@elysiajs/static'
import { auth } from './auth'
import 'unocss'
 
// ... (保留之前的代码)
 
const app = new Elysia()
  .use(html())
  .use(staticPlugin())
  .use(auth)
  .get('/', ({ html, user }) => html`
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Job Board</title>
        <script src="https://unpkg.com/htmx.org@1.9.2"></script>
        <script src="https://unpkg.com/unocss@0.51.8/dist/unocss.js"></script>
      </head>
      <body class="p-4" id="main-body">
        <header class="flex justify-between items-center mb-4">
          <h1 class="text-2xl font-bold">Job Board</h1>
          ${user
            ? `<div>
                Welcome, ${user.name}!
                <a href="/auth/logout" class="ml-2 text-blue-500">Logout</a>
              </div>`
            : `<div>
                <a href="/auth/google" class="text-blue-500 mr-2">Login with Google</a>
                <a href="/auth/github" class="text-blue-500">Login with GitHub</a>
              </div>`
          }
        </header>
        ${user
          ? `<div class="mb-4">
              <label for="template-select">Choose a template:</label>
              <select id="template-select" hx-get="/apply-template" hx-target="#main-body" hx-swap="outerHTML">
                <option value="sunny">Sunny Sky</option>
                <option value="night">Night Sky</option>
                <option value="sunset">Red Sunset</option>
                <option value="rainy">Gentle Rain</option>
                <option value="snowy">Winter Snow</option>
              </select>
              <button hx-get="/edit-template" class="ml-2 bg-blue-500 text-white p-2 rounded">Edit Template</button>
            </div>
            <div id="job-list" hx-get="/jobs" hx-trigger="load"></div>
            <form hx-post="/jobs" hx-target="#job-list" class="mt-4">
              <input type="text" name="title" placeholder="Job Title" required class="border p-2 mb-2 w-full">
              <input type="text" name="company" placeholder="Company" required class="border p-2 mb-2 w-full">
              <textarea name="description" placeholder="Job Description" required class="border p-2 mb-2 w-full h-32"></textarea>
              <button type="submit" class="bg-blue-500 text-white p-2 rounded">Post Job</button>
            </form>`
          : `<p>Please login to view and post jobs.</p>`
        }
      </body>
    </html>
  `)
  // ... (保留之前的路由)
  .listen (3000)
 
console.log(`Elysia is running at ${app.server?.hostname}:${app.server?.port}`)
  1. 更新其他路由以检查用户认证:
.get('/jobs', async ({ user }) => {
  if (!user) return 'Please login to view jobs.'
  const jobs = await db.execute('SELECT * FROM jobs ORDER BY created_at DESC')
  // ... (返回职位列表)
})
 
.post('/jobs', async ({ body, user }) => {
  If (! User) return 'Please login to post jobs.'
  const { title, company, description } = body
  // ... (创建新职位)
})
 
.get('/apply-template', ({ query, user }) => {
  if (!user) return 'Please login to apply templates.'
  // ... (应用模板)
})
 
.get('/edit-template', ({ html, user }) => {
  if (!user) return 'Please login to edit templates.'
  // ... (编辑模板)
})
 
.post('/save-template', ({ body, user }) => {
  if (!user) return 'Please login to save templates.'
  // ... (保存模板)
})
  1. 确保在你的环境变量中设置了必要的配置:
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
JWT_SECRET=your_jwt_secret

这个实现添加了以下功能:

  1. 使用 Google 和 GitHub 的 OAuth2认证
  2. 用户登录和注销功能
  3. 基于 JWT 的会话管理
  4. 根据用户认证状态显示不同的内容
  5. 限制某些操作只能由已登录用户执行

请注意, 这个实现仍然是基本的, 你可能需要根据你的具体需求进行进一步的改进, 例如:

  • 添加用户角色和权限管理
  • 实现用户个人资料页面
  • 添加更多的认证提供商
  • 改进错误处理和用户反馈
  • 实现更安全的密钥管理策略

此外, 在生产环境中, 你应该使用 HTTPS 来保护用户数据和认证过程。