Skip to content

Commit

Permalink
deleted hubSession
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexErrant committed Aug 8, 2023
1 parent 9375ec1 commit d6fbd73
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 90 deletions.
48 changes: 28 additions & 20 deletions design-decisions/example.pentive.secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,42 @@ export productionAlphaKey=
export cloudflareAccountId=
export developmentPlanetscaleDbUrl=
export productionPlanetscaleDbUrl=
# generate 32 bits of random data in base64, cut off new line character, add it to the clipboard
# openssl rand -base64 32 | head -c -1 | pbcopy
export developmentMediaTokenSecret=you+should+replace+this+with+a+real+base64+=
export productionMediaTokenSecret=you+should+replace+this+with+a+real+base64+=
export developmentHubSessionSecret=secret/you+can+run+the+command+which+kinda+=
export productionHubSessionSecret=secret/you+can+run+the+command+which+kinda+=
export developmentJwsSecret=looks+like+openssl+rand+base64+32+comma+but=
export productionJwsSecret=looks+like+openssl+rand+base64+32+comma+but=
export developmentCsrfSecret=with+spaces+and+hyphens+and+stuff+ZZZZZZZZZ=
export productionCsrfSecret=with+spaces+and+hyphens+and+stuff+ZZZZZZZZZ=
export developmentOauthStateSecret=also+this+is+the+same+32+bits+of+base64+joy=
export productionOauthStateSecret=also+this+is+the+same+32+bits+of+base64+joy=
export developmentOauthCodeVerifierSecret=also+this+is+the+same+32+bits+of+base64+joy=
export productionOauthCodeVerifierSecret=also+this+is+the+same+32+bits+of+base64+joy=
export developmentHubInfoSecret=also+this+is+the+same+32+bits+of+base64+joy=
export productionHubInfoSecret=also+this+is+the+same+32+bits+of+base64+joy=
export developmentDiscordId=create at https://discord.com/developers/applications
export productionDiscordId=Redirects (callbacks) are https://pentive.localhost:3014/api/auth/callback/discord or https://pentive.com/api/auth/callback/discord

# the following secrets may be generated as follows:
# openssl rand -base64 32 | head -c -1 | pbcopy
# this generates 32 bits of random data in base64, cuts off the new line character, and adds it to the clipboard
export developmentMediaTokenSecret=
export productionMediaTokenSecret=
export developmentJwsSecret=
export productionJwsSecret=
export developmentCsrfSecret=
export productionCsrfSecret=
export developmentOauthStateSecret=
export productionOauthStateSecret=
export developmentOauthCodeVerifierSecret=
export productionOauthCodeVerifierSecret=
export developmentHubInfoSecret=
export productionHubInfoSecret=

# create at https://discord.com/developers/applications
# Redirects (callbacks) are https://pentive.localhost:3014/api/auth/callback/discord or https://pentive.com/api/auth/callback/discord
export developmentDiscordId=
export productionDiscordId=
export developmentDiscordSecret=
export productionDiscordSecret=
export developmentGithubId=create at https://github.com/settings/developers
export productionGithubId=Authorization callback URL is https://pentive.localhost:3014/api/auth/callback/github or https://pentive.com/api/auth/callback/github

# create at https://github.com/settings/developers
# Authorization callback URL is https://pentive.localhost:3014/api/auth/callback/github or https://pentive.com/api/auth/callback/github
export developmentGithubId=
export productionGithubId=
export developmentGithubSecret=
export productionGithubSecret=

export developmentAppOrigin=https://app.pentive.localhost:3013
export productionAppOrigin=https://app.pentive.com
export developmentHubOrigin=https://pentive.localhost:3014
export productionHubOrigin=https://pentive.com

# If you add any `VITE_PRODUCTION_*`, also update `cicd.yml`
export VITE_DEVELOPMENT_AG_GRID_LICENSE=
export VITE_PRODUCTION_AG_GRID_LICENSE=
Expand Down
2 changes: 1 addition & 1 deletion hub/src/components/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Nav(): JSX.Element {
async (_, { request }) => await getUserId(request)
)
const [, { Form }] = createServerAction$(
async (f: FormData, { request }) => await logout(request)
async (_: FormData) => await logout()
)
return (
<header class="header">
Expand Down
1 change: 0 additions & 1 deletion hub/src/entry-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export default createHandler(
/* eslint-disable solid/reactivity -- event.env and event.responseHeaders should never change at runtime */
setKysely(event.env.planetscaleDbUrl)
setSessionStorage({
sessionSecret: event.env.hubSessionSecret,
jwsSecret: event.env.jwsSecret,
csrfSecret: event.env.csrfSecret,
hubInfoSecret: event.env.hubInfoSecret,
Expand Down
1 change: 0 additions & 1 deletion hub/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { type Base64 } from "shared"

export interface EnvVars {
planetscaleDbUrl: string
hubSessionSecret: Base64
jwsSecret: Base64
csrfSecret: Base64
alphaKey: string
Expand Down
10 changes: 2 additions & 8 deletions hub/src/routes/n/[nook]/submit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import {
createServerData$,
redirect,
} from "solid-start/server"
import {
requireCsrfSignature,
requireSession,
requireJwt,
isInvalidCsrf,
} from "~/session"
import { requireCsrfSignature, requireJwt, isInvalidCsrf } from "~/session"

export function routeData({ params }: RouteDataArgs) {
const nook = (): string => params.nook
Expand Down Expand Up @@ -70,7 +65,6 @@ export default function Submit(): JSX.Element {
if (Object.values(fieldErrors).some(Boolean)) {
throw new FormError("Some fields are invalid", { fieldErrors, fields })
}
const session = await requireSession(request)
const jwt = await requireJwt(request)
if (await isInvalidCsrf(csrfSignature, jwt.jti)) {
const searchParams = new URLSearchParams([
Expand All @@ -81,7 +75,7 @@ export default function Submit(): JSX.Element {

await insertPost({
id: ulidAsHex(),
authorId: session.userId,
authorId: jwt.sub,
title,
text,
nook,
Expand Down
63 changes: 5 additions & 58 deletions hub/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,16 @@ import {
} from "shared"
import { base64ToArray } from "shared-edge"
import { redirect } from "solid-start/server"
import { createCookieSessionStorage } from "solid-start/session"
import { type Cookie, type CookieOptions } from "solid-start/session/cookies"
import { type Session, type SessionStorage } from "solid-start/session/sessions"
import { createPlainCookie } from "~/createPlainCookie"

const sessionUserId = "userId"
const sessionNames = [sessionUserId] as const
type SessionName = (typeof sessionNames)[number]
export type UserSession = { [K in SessionName]: string }

export function setSessionStorage(x: {
sessionSecret: Base64
jwsSecret: Base64
csrfSecret: Base64
hubInfoSecret: Base64
oauthStateSecret: Base64
oauthCodeVerifierSecret: Base64
}): void {
// highTODO consider removing this when adding Auth.js. We need cross-domain auth, and I'm not sure why this exists if we're using a JWT
storage = createCookieSessionStorage({
cookie: {
name: "__Host-session",
secure: true,
secrets: [x.sessionSecret],
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 days
httpOnly: true,
// domain: "", // intentionally missing https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#cookie-with-__host-prefix
// expires: "", // intentionally missing because docs say it's calculated off `maxAge` when missing https://github.com/solidjs/solid-start/blob/1b22cad87dd7bd74f73d807e1d60b886e753a6ee/packages/start/session/cookies.ts#L56-L57
},
})
jwsSecret = base64ToArray(x.jwsSecret)
csrfSecret = x.csrfSecret
hubInfoSecret = base64ToArray(x.hubInfoSecret)
Expand Down Expand Up @@ -140,8 +118,6 @@ export function setSessionStorage(x: {
hubInfoCookie = createPlainCookie(hubInfoCookieName, hubInfoCookieOpts)
}

// @ts-expect-error session calls should throw null error if not setup
let storage = null as SessionStorage
// @ts-expect-error calls should throw null error if not setup
let jwtCookie = null as Cookie
// @ts-expect-error calls should throw null error if not setup
Expand All @@ -167,10 +143,6 @@ let csrfSecret = null as string
// @ts-expect-error calls should throw null error if not setup
let hubInfoSecret = null as Uint8Array

export async function getUserSession(request: Request): Promise<Session> {
return await storage.getSession(request.headers.get("Cookie"))
}

export async function getCsrfSignature(
request: Request
): Promise<string | null> {
Expand Down Expand Up @@ -204,7 +176,7 @@ export async function getOauthCodeVerifier(request: Request) {
}

export interface Jwt {
sub: string
sub: UserId
jti: string
}

Expand All @@ -219,16 +191,14 @@ export async function getJwt(request: Request): Promise<Jwt | null> {
return jwt == null
? null
: {
sub: jwt.payload.sub ?? throwExp("`sub` is empty"),
sub: (jwt.payload.sub as UserId) ?? throwExp("`sub` is empty"),
jti: jwt.payload.jti ?? throwExp("`jti` is empty"),
}
}

export async function getUserId(request: Request) {
const session = await getUserSession(request)
const userId = session.get(sessionUserId) as unknown
if (typeof userId !== "string" || userId.length === 0) return null
return userId as UserId
const jwt = await getJwt(request)
return jwt?.sub ?? null
}

export async function requireUserId(
Expand All @@ -243,24 +213,6 @@ export async function requireUserId(
return r
}

export async function requireSession(
request: Request,
redirectTo: string = new URL(request.url).pathname
): Promise<UserSession> {
const session = await getUserSession(request)
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const r = {} as UserSession
for (const sessionName of sessionNames) {
const sessionValue = session.get(sessionName) as unknown
if (typeof sessionValue !== "string" || sessionValue.length === 0) {
const searchParams = new URLSearchParams([["redirectTo", redirectTo]])
throw redirect(`/login?${searchParams.toString()}`) as unknown
}
r[sessionName] = sessionValue
}
return r
}

export async function requireCsrfSignature(
request: Request,
redirectTo: string = new URL(request.url).pathname
Expand All @@ -285,10 +237,8 @@ export async function requireJwt(
return jwt
}

export async function logout(request: Request): Promise<Response> {
const session = await storage.getSession(request.headers.get("Cookie"))
export async function logout() {
const headers = new Headers()
headers.append("Set-Cookie", await storage.destroySession(session)) // lowTODO parallelize
headers.append("Set-Cookie", await destroyJwtCookie.serialize("")) // lowTODO parallelize
headers.append("Set-Cookie", await destroyCsrfSignatureCookie.serialize("")) // lowTODO parallelize
return redirect("/login", {
Expand All @@ -300,11 +250,8 @@ export async function createUserSession(
userId: string,
redirectTo: string
): Promise<Response> {
const session = await storage.getSession()
session.set(sessionUserId, userId)
const [csrf, csrfSignature] = await generateCsrf()
const headers = new Headers()
headers.append("Set-Cookie", await storage.commitSession(session)) // lowTODO parallelize
const jwt = await generateJwt(userId, csrf)
headers.append("Set-Cookie", await jwtCookie.serialize(jwt)) // lowTODO parallelize
// https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie
Expand Down
1 change: 0 additions & 1 deletion mkenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ source ../PentiveSecrets/secrets.sh
# echo $productionGithubId | npx wrangler secret put githubId --name hub
# echo $productionGithubSecret | npx wrangler secret put githubSecret --name hub
# echo $productionPlanetscaleDbUrl | npx wrangler secret put planetscaleDbUrl --name hub
# echo $productionHubSessionSecret | npx wrangler secret put hubSessionSecret --name hub
# echo $productionCsrfSecret | npx wrangler secret put csrfSecret --name hub
# echo $productionOauthStateSecret | npx wrangler secret put oauthStateSecret --name hub
# echo $productionOauthCodeVerifierSecret | npx wrangler secret put oauthCodeVerifierSecret --name hub
Expand Down

0 comments on commit d6fbd73

Please sign in to comment.