feat(csms): add deployment script and database schema for user authentication
This commit is contained in:
71
apps/csms/src/db/auth-schema.ts
Normal file
71
apps/csms/src/db/auth-schema.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
|
||||
|
||||
export const user = pgTable("user", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull().unique(),
|
||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||
image: text("image"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
username: text("username").unique(),
|
||||
displayUsername: text("display_username"),
|
||||
role: text("role").default("user"),
|
||||
});
|
||||
|
||||
export const session = pgTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
ipAddress: text("ip_address"),
|
||||
userAgent: text("user_agent"),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const account = pgTable("account", {
|
||||
id: text("id").primaryKey(),
|
||||
accountId: text("account_id").notNull(),
|
||||
providerId: text("provider_id").notNull(),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
accessToken: text("access_token"),
|
||||
refreshToken: text("refresh_token"),
|
||||
idToken: text("id_token"),
|
||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||
scope: text("scope"),
|
||||
password: text("password"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
});
|
||||
|
||||
export const verification = pgTable("verification", {
|
||||
id: text("id").primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at")
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||
.notNull(),
|
||||
});
|
||||
|
||||
export const jwks = pgTable("jwks", {
|
||||
id: text("id").primaryKey(),
|
||||
publicKey: text("public_key").notNull(),
|
||||
privateKey: text("private_key").notNull(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
});
|
||||
1
apps/csms/src/db/schema.ts
Normal file
1
apps/csms/src/db/schema.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './auth-schema.ts'
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg from '../../../package.json' with { type: 'json' }
|
||||
import 'dotenv/config'
|
||||
import { serve } from '@hono/node-server'
|
||||
import { Hono } from 'hono'
|
||||
import { createNodeWebSocket } from '@hono/node-ws'
|
||||
@@ -6,25 +6,48 @@ import { getConnInfo } from '@hono/node-server/conninfo'
|
||||
import { cors } from 'hono/cors'
|
||||
import { logger } from 'hono/logger'
|
||||
import { showRoutes } from 'hono/dev'
|
||||
import { auth } from './lib/auth.ts'
|
||||
|
||||
const app = new Hono()
|
||||
const app = new Hono<{
|
||||
Variables: {
|
||||
user: typeof auth.$Infer.Session.user | null
|
||||
session: typeof auth.$Infer.Session.session | null
|
||||
}
|
||||
}>()
|
||||
|
||||
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })
|
||||
|
||||
app.use(logger())
|
||||
|
||||
app.use('/ocpp', cors({
|
||||
origin: '*',
|
||||
allowMethods: ['GET', 'POST', 'OPTIONS'],
|
||||
allowHeaders: ['Content-Type', 'Authorization'],
|
||||
exposeHeaders: ['Content-Length'],
|
||||
credentials: true,
|
||||
}))
|
||||
app.use('*', async (c, next) => {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers })
|
||||
if (!session) {
|
||||
c.set('user', null)
|
||||
c.set('session', null)
|
||||
await next()
|
||||
return
|
||||
}
|
||||
c.set('user', session.user)
|
||||
c.set('session', session.session)
|
||||
await next()
|
||||
})
|
||||
|
||||
app.use(
|
||||
'/api/auth/*',
|
||||
cors({
|
||||
origin: '*',
|
||||
allowMethods: ['GET', 'POST', 'OPTIONS'],
|
||||
allowHeaders: ['Content-Type', 'Authorization'],
|
||||
exposeHeaders: ['Content-Length'],
|
||||
credentials: true,
|
||||
}),
|
||||
)
|
||||
|
||||
app.on(['POST', 'GET'], '/api/auth/*', (c) => auth.handler(c.req.raw))
|
||||
|
||||
app.get('/', (c) => {
|
||||
return c.json({
|
||||
platform: 'Helios CSMS',
|
||||
version: pkg.version,
|
||||
message: 'ok',
|
||||
})
|
||||
})
|
||||
@@ -35,7 +58,9 @@ app.get(
|
||||
return {
|
||||
onOpen(evt, ws) {
|
||||
const connInfo = getConnInfo(c)
|
||||
console.log(`New connection from ${connInfo.remote.address}:${connInfo.remote.port}`)
|
||||
console.log(
|
||||
`New connection from ${connInfo.remote.address}:${connInfo.remote.port}`,
|
||||
)
|
||||
},
|
||||
onMessage(evt, ws) {
|
||||
console.log(`Received message: ${evt.data}`)
|
||||
|
||||
27
apps/csms/src/lib/auth.ts
Normal file
27
apps/csms/src/lib/auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||
import { useDrizzle } from './db.js'
|
||||
import * as schema from '@/db/schema.ts'
|
||||
import { bearer, jwt, username } from 'better-auth/plugins'
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(useDrizzle(), {
|
||||
provider: 'pg',
|
||||
schema: {
|
||||
...schema,
|
||||
},
|
||||
}),
|
||||
user: {
|
||||
additionalFields: {
|
||||
role: {
|
||||
type: 'string',
|
||||
defaultValue: 'user',
|
||||
input: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [username(), bearer(), jwt()],
|
||||
})
|
||||
24
apps/csms/src/lib/db.ts
Normal file
24
apps/csms/src/lib/db.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres'
|
||||
import { Pool } from 'pg'
|
||||
import * as schema from '@/db/schema.js'
|
||||
|
||||
let pgPoolInstance: Pool | null = null
|
||||
let drizzleInstance: ReturnType<typeof drizzle> | null = null
|
||||
|
||||
export const useDrizzle = () => {
|
||||
if (!pgPoolInstance || !drizzleInstance) {
|
||||
pgPoolInstance = new Pool({
|
||||
connectionString: process.env.DATABASE_CONNECTION_STRING,
|
||||
})
|
||||
drizzleInstance = drizzle({ client: pgPoolInstance, schema })
|
||||
}
|
||||
return drizzleInstance
|
||||
}
|
||||
|
||||
export const closeDrizzle = () => {
|
||||
if (pgPoolInstance) {
|
||||
pgPoolInstance.end()
|
||||
pgPoolInstance = null
|
||||
drizzleInstance = null
|
||||
}
|
||||
}
|
||||
0
apps/csms/src/types/db.ts
Normal file
0
apps/csms/src/types/db.ts
Normal file
Reference in New Issue
Block a user