Skip to content

Commit

Permalink
Refactor authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
phisn committed Apr 1, 2024
1 parent a6d88ca commit 33486f5
Show file tree
Hide file tree
Showing 29 changed files with 522 additions and 329 deletions.
2 changes: 1 addition & 1 deletion packages/rust-game/src/player_plugin/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct CameraConfig {
impl Default for CameraConfig {
fn default() -> Self {
Self {
zoom: 1920.0 * 0.02,
zoom: 1920.0 * 0.05,
animation_speed: 1.0,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { and, eq } from "drizzle-orm"
import { Buffer } from "node:buffer"
import { z } from "zod"
import { leaderboard } from "../db-schema"
import { publicProcedure, router } from "../trpc"
import { leaderboard } from "../../framework/db-schema"
import { publicProcedure, router } from "../../framework/trpc"

export const leaderboardRouter = router({
get: publicProcedure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { ReplayModel } from "runtime/proto/replay"
import { WorldModel } from "runtime/proto/world"
import { validateReplay } from "runtime/src/model/replay/validate-replay"
import { z } from "zod"
import { leaderboard } from "../db-schema"
import { worlds } from "../domain/worlds"
import { publicProcedure } from "../trpc"
import { worlds } from "../../domain/worlds"
import { leaderboard } from "../../framework/db-schema"
import { publicProcedure } from "../../framework/trpc"

export const validateReplayProcedure = publicProcedure
.input(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,60 @@ import jwt from "@tsndr/cloudflare-worker-jwt"
import { eq } from "drizzle-orm"
import { OAuth2RequestError } from "oslo/oauth2"
import { z } from "zod"
import { users } from "../db-schema"
import { publicProcedure, router } from "../trpc"
import { JwtCreationToken } from "../../domain/auth/jwt-creation-token"
import { JwtToken } from "../../domain/auth/jwt-token"
import { users } from "../../framework/db-schema"
import { publicProcedure, router } from "../../framework/trpc"

export const userRouter = router({
me: publicProcedure.query(async ({ ctx: { db, user } }) => {
if (!user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Not logged in",
})
}

const [dbUser] = await db.select().from(users).where(eq(users.id, user.id))

if (!dbUser) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "User not found",
})
}

return { username: dbUser.username }
}),
rename: publicProcedure
.input(
z
.object({
username: z.string(),
})
.required(),
)
.mutation(async ({ input: { username }, ctx: { db, user } }) => {
if (!user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Not logged in",
})
}

const duplicate = await db.select().from(users).where(eq(users.username, username))

if (duplicate.length > 0) {
return {
message: "username-taken" as const,
}
}

await db.update(users).set({ username }).where(eq(users.id, user.id))
console.log("renamed user", user.id, "to", username)

export const googleAuthRouter = router({
return { message: "success" as const }
}),
getToken: publicProcedure
.input(
z
Expand Down Expand Up @@ -59,17 +109,18 @@ export const googleAuthRouter = router({
env.JWT_SECRET,
)

return { tokenForCreation, type: "prompt-create" }
return { tokenForCreation, type: "prompt-create" as const }
}

const token = await jwt.sign(
const token = await jwt.sign<JwtToken>(
{
username: user.username,
id: user.id,
iat: Date.now(),
},
env.JWT_SECRET,
)

return { token, username: user.username, type: "logged-in" }
return { token, type: "logged-in" as const }
} catch (e) {
if (e instanceof OAuth2RequestError) {
console.error(e)
Expand All @@ -91,7 +142,7 @@ export const googleAuthRouter = router({
})
.required(),
)
.query(async ({ input: { tokenForCreation, username }, ctx: { env, db } }) => {
.mutation(async ({ input: { tokenForCreation, username }, ctx: { env, db } }) => {
const verification = await jwt.verify(tokenForCreation, env.JWT_SECRET)

if (verification === false) {
Expand All @@ -101,7 +152,7 @@ export const googleAuthRouter = router({
})
}

const payload = jwt.decode<{ email: string }>(tokenForCreation).payload
const payload = jwt.decode<JwtCreationToken>(tokenForCreation).payload

if (!payload) {
throw new TRPCError({
Expand All @@ -110,20 +161,39 @@ export const googleAuthRouter = router({
})
}

const duplicate = await db.select().from(users).where(eq(users.username, username))

if (duplicate.length > 0) {
return {
message: "username-taken" as const,
}
}

const { email } = payload

const token = await jwt.sign(
const [user] = await db
.insert(users)
.values({
username: username,
email: email,
})
.returning({ id: users.id })

if (!user) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Failed to create user",
})
}

const token = await jwt.sign<JwtToken>(
{
username,
id: user.id,
iat: Date.now(),
},
env.JWT_SECRET,
)

await db.insert(users).values({
username: username,
email: email,
})

return { token }
return { message: "success" as const, jwt: token }
}),
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { TRPCError } from "@trpc/server"
import { asc, eq, inArray } from "drizzle-orm"
import { WorldView } from "shared/src/views/world-view"
import { z } from "zod"
import { leaderboard } from "../db-schema"
import { worlds } from "../domain/worlds"
import { publicProcedure, router } from "../trpc"
import { worlds } from "../../domain/worlds"
import { leaderboard } from "../../framework/db-schema"
import { publicProcedure, router } from "../../framework/trpc"

export const worldRouter = router({
get: publicProcedure
Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/domain/auth/jwt-creation-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface JwtCreationToken {
email: string
iat: number
}
4 changes: 4 additions & 0 deletions packages/server/src/domain/auth/jwt-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface JwtToken {
id: number
iat: number
}
3 changes: 3 additions & 0 deletions packages/server/src/domain/auth/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface User {
id: number
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export const users = sqliteTable("users", {
autoIncrement: true,
}),

email: text("email").notNull(),
username: text("username").notNull(),
email: text("email").notNull().unique(),
username: text("username").notNull().unique(),
})

export type Users = typeof users.$inferSelect
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import jwt from "@tsndr/cloudflare-worker-jwt"
import { JwtToken } from "../../domain/auth/jwt-token"
import { User } from "../../domain/auth/user"
import { Env } from "../../env"

export function userFromAuthorizationHeader(env: Env, authorization: string | null): User | null {
try {
if (authorization && authorization !== "undefined") {
console.log("authorization", authorization)
if (!jwt.verify(authorization, env.JWT_SECRET)) {
return null
}

const payload = jwt.decode<JwtToken>(authorization).payload

if (!payload) {
return null
}

return { id: payload.id }
}
} catch (e) {
console.error(e)
}

return null
}
14 changes: 14 additions & 0 deletions packages/server/src/framework/trpc-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { leaderboardRouter } from "../api/leaderboard/leaderboard-router"
import { validateReplayProcedure } from "../api/leaderboard/validate-replay"
import { userRouter } from "../api/user/user-router"
import { worldRouter } from "../api/world/world-router"
import { router } from "./trpc"

export const appRouter = router({
user: userRouter,
world: worldRouter,
replay: leaderboardRouter,
validateReplay: validateReplayProcedure,
})

export type AppRouter = typeof appRouter
49 changes: 49 additions & 0 deletions packages/server/src/framework/trpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { inferAsyncReturnType, initTRPC } from "@trpc/server"
import { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"
import { drizzle } from "drizzle-orm/d1"
import { OAuth2Client } from "oslo/oauth2"
import superjson from "superjson"
import { Env } from "../env"
import { userFromAuthorizationHeader } from "./helper/user-from-authorization-header"

export const createContext =
(env: Env) =>
({ req }: FetchCreateContextFnOptions) => {
const user = userFromAuthorizationHeader(env, req.headers.get("Authorization"))

return {
db: drizzle(env.DB),
oauth: new OAuth2Client(
env.AUTH_GOOGLE_CLIENT_ID,
"https://accounts.google.com/o/oauth2/auth",
"https://accounts.google.com/o/oauth2/token",
{
redirectURI: `${env.CLIENT_URL}`,
},
),
env,
user,
}
}

type Context = inferAsyncReturnType<inferAsyncReturnType<typeof createContext>>

const t = initTRPC.context<Context>().create({
transformer: superjson,
})

export const middleware = t.middleware
export const router = t.router

export const logger = middleware(async opts => {
try {
// format date as HH:MM:SS
console.log(`Req ${new Date().toISOString()}: ${opts.path}`)
return await opts.next()
} catch (e) {
console.error(e)
throw e
}
})

export const publicProcedure = t.procedure.use(logger)
6 changes: 3 additions & 3 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"
import { Env } from "./env"
import { createContext } from "./trpc"
import { appRouter } from "./trpc-router"
import { createContext } from "./framework/trpc"
import { appRouter } from "./framework/trpc-router"

export default {
async fetch(request: Request, env: Env): Promise<Response> {
Expand All @@ -12,7 +12,7 @@ export default {
const headers = {
"Access-Control-Allow-Origin": "",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Allow-Credentials": "true",
}

Expand Down
14 changes: 0 additions & 14 deletions packages/server/src/trpc-router.ts

This file was deleted.

40 changes: 0 additions & 40 deletions packages/server/src/trpc.ts

This file was deleted.

Loading

0 comments on commit 33486f5

Please sign in to comment.