Skip to content

Commit

Permalink
feat: middleware auth
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Jul 13, 2024
1 parent 1305436 commit ec9daf0
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 119 deletions.
4 changes: 2 additions & 2 deletions apps/core/src/modules/auth/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {
users,
verificationTokens,
} from '@meta-muse/drizzle/schema'
import { CreateAuth, type FastifyAuthConfig } from './auth.implement'
import { CreateAuth, type ServerAuthConfig } from './auth.implement'

const {
providers: { github: GitHub },
} = authjs

export const authConfig: FastifyAuthConfig = {
export const authConfig: ServerAuthConfig = {
basePath: isDev ? '/auth' : `/api/v${API_VERSION}/auth`,
secret: AUTH.secret,
callbacks: {
Expand Down
18 changes: 0 additions & 18 deletions apps/core/src/modules/auth/auth.controller.ts

This file was deleted.

149 changes: 149 additions & 0 deletions apps/core/src/modules/auth/auth.implement.deprated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
Auth,
type AuthConfig,
type Session,
createActionURL,
setEnvDefaults,
} from '@meta-muse/complied'

import { API_VERSION } from '@core/app.config'
import { isDev } from '@core/global/env.global'
import type { FastifyReply, FastifyRequest } from 'fastify'

export type FastifyAuthConfig = Omit<AuthConfig, 'raw'>
export type GetSessionResult = Promise<Session | null>

export async function getSessionBase(
req: FastifyRequest,
config: FastifyAuthConfig,
) {
setEnvDefaults(process.env, config)

const url = createActionURL(
'session',
req.protocol,
// @ts-expect-error
new Headers(req.headers),
process.env,
config.basePath,
)

const response = await Auth(
new Request(url, { headers: { cookie: req.headers.cookie ?? '' } }),
config,
)

const { status = 200 } = response

const data = await response.json()

if (!data || !Object.keys(data).length) return null
if (status === 200) return data
}

function getBasePath() {
return isDev ? '/auth' : `/api/v${API_VERSION}/auth`
}

export function CreateAuth(config: FastifyAuthConfig) {
return async (req: FastifyRequest, res: FastifyReply) => {
try {
config.basePath = getBasePath()
setEnvDefaults(process.env, config)

const auth = await Auth(toWebRequest(req), config)

await toFastifyResponse(req, auth, res)
} catch (error) {
// throw error
res.send(error.message)
}
}
}

export function toWebRequest(req: FastifyRequest) {
const url = `${req.protocol}://${req.hostname}${req.url}`

const headers = new Headers()

Object.entries(req.headers).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((v) => {
v && headers.append(key, v)
})
return
}

value && headers.append(key, value)
})

// GET and HEAD not allowed to receive body
const body = /GET|HEAD/.test(req.method) ? undefined : encodeRequestBody(req)

const request = new Request(url, {
method: req.method,
headers,
body,
})

return request
}

function encodeRequestBody(req: FastifyRequest) {
const contentType = req.headers['content-type']

if (contentType?.includes('application/x-www-form-urlencoded')) {
return encodeUrlEncoded(req.body as Record<string, any>)
}

if (contentType?.includes('application/json')) {
return encodeJson(req.body as Record<string, any>)
}

return req.body as ReadableStream | string | undefined
}

function encodeUrlEncoded(object: Record<string, any> = {}) {
const params = new URLSearchParams()

for (const [key, value] of Object.entries(object)) {
if (Array.isArray(value)) {
value.forEach((v) => params.append(key, v))
} else {
params.append(key, value)
}
}

return params.toString()
}

function encodeJson(obj: Record<string, any>) {
return JSON.stringify(obj)
}

async function toFastifyResponse(
req: FastifyRequest,
response: Response,
res: FastifyReply,
) {
response.headers.forEach((value, key) => {
if (value) {
res.header(key, value)
}
})

res.status(response.status)

res.header('Content-Type', response.headers.get('content-type') || '')
res.header('access-control-allow-methods', 'GET, POST')
res.header('access-control-allow-headers', 'content-type')
res.header(
'access-control-allow-origin',
req.headers.origin || req.headers.referer || req.headers.host || '*',
)
res.header('access-control-allow-credentials', 'true')

const text = await response.text()

res.send(text)
}
115 changes: 32 additions & 83 deletions apps/core/src/modules/auth/auth.implement.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import {
Auth,
type AuthConfig,
type Session,
createActionURL,
setEnvDefaults,
type AuthConfig,
type Session,
} from '@meta-muse/complied'

import { API_VERSION } from '@core/app.config'
import { isDev } from '@core/global/env.global'
import type { FastifyReply, FastifyRequest } from 'fastify'
import type { IncomingMessage, ServerResponse } from 'http'
import { getRequest } from './req.util'

export type FastifyAuthConfig = Omit<AuthConfig, 'raw'>
export type ServerAuthConfig = Omit<AuthConfig, 'raw'>
export type GetSessionResult = Promise<Session | null>

export async function getSessionBase(
req: FastifyRequest,
config: FastifyAuthConfig,
req: IncomingMessage,
config: ServerAuthConfig,
) {
setEnvDefaults(process.env, config)

const protocol = (req.headers['x-forwarded-proto'] || 'http') as string
const url = createActionURL(
'session',
req.protocol,
protocol,
// @ts-expect-error

new Headers(req.headers),
process.env,
config.basePath,
Expand All @@ -45,105 +47,52 @@ function getBasePath() {
return isDev ? '/auth' : `/api/v${API_VERSION}/auth`
}

export function CreateAuth(config: FastifyAuthConfig) {
return async (req: FastifyRequest, res: FastifyReply) => {
export function CreateAuth(config: ServerAuthConfig) {
return async (req: IncomingMessage, res: ServerResponse) => {
try {
config.basePath = getBasePath()
setEnvDefaults(process.env, config)

const auth = await Auth(toWebRequest(req), config)
const auth = await Auth(await toWebRequest(req), config)

await toFastifyResponse(req, auth, res)
await toServerResponse(req, auth, res)
} catch (error) {
console.error(error)
// throw error
res.send(error.message)
res.end(error.message)
}
}
}

export function toWebRequest(req: FastifyRequest) {
const url = `${req.protocol}://${req.hostname}${req.url}`

const headers = new Headers()

Object.entries(req.headers).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((v) => {
v && headers.append(key, v)
})
return
}

value && headers.append(key, value)
})

// GET and HEAD not allowed to receive body
const body = /GET|HEAD/.test(req.method) ? undefined : encodeRequestBody(req)
export async function toWebRequest(req: IncomingMessage) {
const host = req.headers.host || 'localhost'
const protocol = req.headers['x-forwarded-proto'] || 'http'
const base = `${protocol}://${host}`

const request = new Request(url, {
method: req.method,
headers,
body,
})

return request
return getRequest(base, req)
}

function encodeRequestBody(req: FastifyRequest) {
const contentType = req.headers['content-type']

if (contentType?.includes('application/x-www-form-urlencoded')) {
return encodeUrlEncoded(req.body as Record<string, any>)
}

if (contentType?.includes('application/json')) {
return encodeJson(req.body as Record<string, any>)
}

return req.body as ReadableStream | string | undefined
}

function encodeUrlEncoded(object: Record<string, any> = {}) {
const params = new URLSearchParams()

for (const [key, value] of Object.entries(object)) {
if (Array.isArray(value)) {
value.forEach((v) => params.append(key, v))
} else {
params.append(key, value)
}
}

return params.toString()
}

function encodeJson(obj: Record<string, any>) {
return JSON.stringify(obj)
}

async function toFastifyResponse(
req: FastifyRequest,
async function toServerResponse(
req: IncomingMessage,
response: Response,
res: FastifyReply,
res: ServerResponse,
) {
response.headers.forEach((value, key) => {
if (value) {
res.header(key, value)
res.setHeader(key, value)
}
})

res.status(response.status)

res.header('Content-Type', response.headers.get('content-type') || '')
res.header('access-control-allow-methods', 'GET, POST')
res.header('access-control-allow-headers', 'content-type')
res.header(
res.setHeader('Content-Type', response.headers.get('content-type') || '')
res.setHeader('access-control-allow-methods', 'GET, POST')
res.setHeader('access-control-allow-headers', 'content-type')
res.setHeader(
'access-control-allow-origin',
req.headers.origin || req.headers.referer || req.headers.host || '*',
)
res.header('access-control-allow-credentials', 'true')
res.setHeader('access-control-allow-credentials', 'true')

const text = await response.text()

res.send(text)
res.writeHead(response.status, response.statusText)
res.end(text)
}
16 changes: 16 additions & 0 deletions apps/core/src/modules/auth/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { NestMiddleware } from '@nestjs/common'
import type { IncomingMessage, ServerResponse } from 'http'
import { authHandler } from './auth.config'

export class AuthMiddleware implements NestMiddleware {
async use(req: IncomingMessage, res: ServerResponse, next: () => void) {
if (req.method !== 'GET' && req.method !== 'POST') {
next()
return
}

await authHandler(req, res)

next()
}
}
17 changes: 13 additions & 4 deletions apps/core/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { Global, Module } from '@nestjs/common'
import {
Global,
Module,
type MiddlewareConsumer,
type NestModule,
} from '@nestjs/common'

import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { AuthMiddleware } from './auth.middleware'

@Module({
imports: [],
controllers: [AuthController],
controllers: [],
providers: [AuthService],
exports: [],
})
@Global()
export class AuthModule {}
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('/auth/(.*)')
}
}
Loading

0 comments on commit ec9daf0

Please sign in to comment.