Added Auth
This commit is contained in:
29
src/lib/auth-client.ts
Normal file
29
src/lib/auth-client.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createAuthClient } from 'better-auth/react'
|
||||
import { adminClient, organizationClient } from 'better-auth/client/plugins'
|
||||
import { ac, admin, user } from '@/lib/auth/permissions'
|
||||
import { acOrg, adminOrg, member, owner } from './auth/organization-permissions'
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: 'http://localhost:3000',
|
||||
plugins: [
|
||||
adminClient({
|
||||
ac,
|
||||
roles: { admin, user },
|
||||
defaultRole: 'user',
|
||||
}),
|
||||
organizationClient({
|
||||
ac: acOrg,
|
||||
roles: { owner, admin: adminOrg, member },
|
||||
schema: {
|
||||
organization: {
|
||||
additionalFields: {
|
||||
color: {
|
||||
type: 'string',
|
||||
defaultValue: '#000000',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
60
src/lib/auth.ts
Normal file
60
src/lib/auth.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { admin as adminPlugin, organization } from 'better-auth/plugins'
|
||||
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
||||
import { prisma } from '@/db'
|
||||
import { ac, admin, user } from '@/lib/auth/permissions'
|
||||
import {
|
||||
acOrg,
|
||||
adminOrg,
|
||||
member,
|
||||
owner,
|
||||
} from '@/lib/auth/organization-permissions'
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: prismaAdapter(prisma, {
|
||||
provider: 'postgresql',
|
||||
}),
|
||||
experimental: { joins: true },
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
requireEmailVerification: false,
|
||||
},
|
||||
trustedOrigins: ['http://localhost:3001'],
|
||||
plugins: [
|
||||
adminPlugin({
|
||||
ac,
|
||||
roles: { admin, user },
|
||||
defaultRole: 'user',
|
||||
}),
|
||||
organization({
|
||||
ac: acOrg,
|
||||
roles: { owner, admin: adminOrg, member },
|
||||
schema: {
|
||||
organization: {
|
||||
additionalFields: {
|
||||
color: {
|
||||
type: 'string',
|
||||
defaultValue: '#000000',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
databaseHooks: {
|
||||
user: {
|
||||
create: {
|
||||
after: async (user) => {
|
||||
await auth.api.createOrganization({
|
||||
body: {
|
||||
name: `${user.name || 'User'}'s Organization`,
|
||||
slug: `${user.name?.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}`,
|
||||
userId: user.id,
|
||||
color: '#000000',
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
37
src/lib/auth/organization-permissions.ts
Normal file
37
src/lib/auth/organization-permissions.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createAccessControl } from 'better-auth/plugins/access'
|
||||
import {
|
||||
defaultStatements,
|
||||
adminAc,
|
||||
ownerAc,
|
||||
} from 'better-auth/plugins/organization/access'
|
||||
|
||||
const statement = {
|
||||
...defaultStatements,
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
} as const
|
||||
|
||||
const acOrg = createAccessControl(statement)
|
||||
|
||||
const owner = acOrg.newRole({
|
||||
...ownerAc.statements,
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
})
|
||||
|
||||
const adminOrg = acOrg.newRole({
|
||||
...adminAc.statements,
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
})
|
||||
|
||||
const member = acOrg.newRole({
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
})
|
||||
|
||||
export { acOrg, owner, adminOrg, member }
|
||||
29
src/lib/auth/permissions.ts
Normal file
29
src/lib/auth/permissions.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { defaultStatements, adminAc } from 'better-auth/plugins/admin/access'
|
||||
import { createAccessControl } from 'better-auth/plugins/access'
|
||||
|
||||
const statement = {
|
||||
...defaultStatements,
|
||||
audit: ['list'],
|
||||
setting: ['list', 'create', 'update', 'delete'],
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
} as const
|
||||
|
||||
const ac = createAccessControl(statement)
|
||||
|
||||
const admin = ac.newRole({
|
||||
...adminAc.statements,
|
||||
audit: ['list'],
|
||||
setting: ['list', 'create', 'update', 'delete'],
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
box: ['list', 'create', 'update', 'delete'],
|
||||
item: ['list', 'create', 'update', 'delete'],
|
||||
})
|
||||
|
||||
const user = ac.newRole({
|
||||
setting: ['list', 'update'],
|
||||
house: ['list', 'create', 'update', 'delete'],
|
||||
})
|
||||
|
||||
export { ac, admin, user }
|
||||
11
src/lib/auth/session.ts
Normal file
11
src/lib/auth/session.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createServerFn } from '@tanstack/react-start'
|
||||
import { getRequestHeaders } from '@tanstack/react-start/server'
|
||||
import { auth } from '../auth'
|
||||
|
||||
export const getSession = createServerFn({ method: 'GET' }).handler(
|
||||
async () => {
|
||||
const headers = getRequestHeaders()
|
||||
const session = await auth.api.getSession({ headers })
|
||||
return session
|
||||
},
|
||||
)
|
||||
44
src/lib/i18n.ts
Normal file
44
src/lib/i18n.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createIsomorphicFn } from '@tanstack/react-start'
|
||||
import { getCookie } from '@tanstack/react-start/server'
|
||||
import i18n from 'i18next'
|
||||
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
import enTranslations from '../locales/en.json'
|
||||
import viTranslations from '../locales/vi.json'
|
||||
|
||||
export const resources = {
|
||||
en: {
|
||||
translation: enTranslations,
|
||||
},
|
||||
vi: {
|
||||
translation: viTranslations,
|
||||
},
|
||||
} as const
|
||||
|
||||
export const defaultNS = 'translation'
|
||||
|
||||
const i18nCookieName = 'i18nextLng'
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
defaultNS,
|
||||
fallbackLng: 'vi',
|
||||
supportedLngs: ['en', 'vi'],
|
||||
detection: {
|
||||
order: ['cookie'],
|
||||
lookupCookie: i18nCookieName,
|
||||
caches: ['cookie'],
|
||||
cookieMinutes: 60 * 24 * 365,
|
||||
},
|
||||
interpolation: { escapeValue: false },
|
||||
})
|
||||
|
||||
export const setSSRLanguage = createIsomorphicFn().server(async () => {
|
||||
const language = getCookie(i18nCookieName)
|
||||
await i18n.changeLanguage(language || 'vi')
|
||||
})
|
||||
|
||||
export default i18n
|
||||
12
src/lib/i18next.d.ts
vendored
Normal file
12
src/lib/i18next.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'i18next'
|
||||
import translation from '../locales/vi.json'
|
||||
import { defaultNS } from './i18n'
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
defaultNS: typeof defaultNS
|
||||
resources: {
|
||||
translation: typeof translation
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/lib/middleware.ts
Normal file
15
src/lib/middleware.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createMiddleware } from '@tanstack/react-start'
|
||||
import { auth } from './auth'
|
||||
import { redirect } from '@tanstack/react-router'
|
||||
|
||||
export const authMiddleware = createMiddleware().server(
|
||||
async ({ next, request }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers,
|
||||
})
|
||||
if (!session) {
|
||||
throw redirect({ to: '/sign-in' })
|
||||
}
|
||||
return await next()
|
||||
},
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { ClassValue } from 'clsx'
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
export function cn(...inputs: Array<ClassValue>) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user