Skip to content

Commit

Permalink
Merge pull request #1057 from hackclub/malted/vuln
Browse files Browse the repository at this point in the history
Don't mark getselfperson as `"use server"` & protect auth actions
  • Loading branch information
malted authored Jan 2, 2025
2 parents f792e08 + 958683c commit 72a43ac
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 380 deletions.
2 changes: 1 addition & 1 deletion src/app/api/referral/[autonum]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server'

import { getPersonByAuto } from '@/app/utils/airtable'
import { getPersonByAuto } from '@/app/utils/server/airtable'
import { redirect } from 'next/navigation'
import { NextRequest } from 'next/server'

Expand Down
6 changes: 1 addition & 5 deletions src/app/api/slack_redirect/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
getRedirectUri,
getSession,
createSlackSession,
} from '@/app/utils/auth'
import { getRedirectUri, createSlackSession } from '@/app/utils/server/auth'
import { redirect } from 'next/navigation'
import { NextRequest } from 'next/server'

Expand Down
39 changes: 38 additions & 1 deletion src/app/harbor/shipyard/ship-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use server'

import { getSelfPerson } from '@/app/utils/airtable'
import { getSelfPerson } from '@/app/utils/server/airtable'
import { getSession } from '@/app/utils/auth'
import { fetchShips, person } from '@/app/utils/data'
import { getWakaSessions } from '@/app/utils/waka'
Expand Down Expand Up @@ -222,6 +222,19 @@ export async function updateShip(ship: Ship) {
throw error
}

const existingShips = (
await base()(shipsTableName)
.select({ filterByFormula: `{entrant__slack_id} = '${session.slackId}'` })
.all()
).filter((s) => s.id === ship.id)
if (!existingShips || existingShips.length === 0) {
const err = new Error(
`Tried to update a ghost ship: ${JSON.stringify(ship)}`,
)
console.error(err)
throw err
}

console.log('updating!', ship)
console.log(ship.yswsType)

Expand Down Expand Up @@ -265,6 +278,19 @@ export async function stagedToShipped(
return { error, ok: false }
}

const existingShips = (
await base()(shipsTableName)
.select({ filterByFormula: `{entrant__slack_id} = '${session.slackId}'` })
.all()
).filter((s) => s.id === ship.id)
if (!existingShips || existingShips.length === 0) {
const err = new Error(
`Tried to promote a ghost ship: ${JSON.stringify(ship)}`,
)
console.error(err)
throw err
}

const p = await person()

if (!p.fields.academy_completed) {
Expand Down Expand Up @@ -334,6 +360,17 @@ export async function deleteShip(shipId: string) {
throw error
}

const existingShips = (
await base()(shipsTableName)
.select({ filterByFormula: `{entrant__slack_id} = '${session.slackId}'` })
.all()
).filter((s) => s.id === shipId)
if (!existingShips || existingShips.length === 0) {
const err = new Error(`Tried to delete a ghost ship: ${shipId}`)
console.error(err)
throw err
}

await new Promise((resolve, reject) => {
base()(shipsTableName).update(
[
Expand Down
15 changes: 1 addition & 14 deletions src/app/harbor/shop/shop-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Airtable from 'airtable'
import { getSession } from '@/app/utils/auth'
import { getSelfPerson } from '@/app/utils/airtable'
import { getSelfPerson } from '@/app/utils/server/airtable'
import { NextResponse } from 'next/server'

const base = () => {
Expand Down Expand Up @@ -32,19 +32,6 @@ export interface ShopItem {
maximumHoursEstimated: number
}

export async function getPerson() {
const session = await getSession()
if (!('slackId' in session)) {
return
}
const person = await getSelfPerson(session.slackId)
if (!person) {
return NextResponse.json(
{ error: "i don't even know who you are" },
{ status: 418 },
)
}
}
export async function getShop(): Promise<ShopItem[]> {
const items: ShopItem[] = []

Expand Down
154 changes: 1 addition & 153 deletions src/app/utils/airtable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,159 +2,8 @@

import { getSession } from './auth'
import { person, updateShowInLeaderboard } from './data'
import { getSelfPerson } from './server/airtable'

export const getSelfPerson = async (slackId: string) => {
const session = await getSession()
if (!session) {
throw new Error('No session when trying to get self person')
}
if (session.slackId !== slackId) {
const err = new Error('Session slackId does not match provided slackId')
console.error(err)
throw err
}

const url = `https://middleman.hackclub.com/airtable/v0/${process.env.BASE_ID}/people`
const filterByFormula = encodeURIComponent(`{slack_id} = '${slackId}'`)
const response = await fetch(`${url}?filterByFormula=${filterByFormula}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`,
'Content-Type': 'application/json',
'User-Agent': 'highseas.hackclub.com (getSelfPerson)',
},
})

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}

let data
try {
data = await response.json()
} catch (e) {
console.error(e, await response.text())
throw e
}
return data.records[0]
}

export const getSignpostUpdates = async () => {
const url = `https://middleman.hackclub.com/airtable/v0/${process.env.BASE_ID}/signpost`
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`,
'Content-Type': 'application/json',
'User-Agent': 'highseas.hackclub.com (getSignpostUpdates)',
},
})

if (!response.ok) {
console.log(response)
throw new Error(`HTTP error! status: ${response.status}`)
}

let data
try {
data = await response.json()
} catch (e) {
console.error(e, await response.text())
throw e
}

return data.records
}

export async function getPersonByAuto(num: string): Promise<{
slackId: string
} | null> {
const baseId = process.env.BASE_ID
const apiKey = process.env.AIRTABLE_API_KEY
const table = 'people'

const url = `https://middleman.hackclub.com/airtable/v0/${baseId}/${table}?filterByFormula={autonumber}='${encodeURIComponent(num)}'`

const response = await fetch(url, {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'User-Agent': 'highseas.hackclub.com (getPersonByMagicToken)',
},
})

if (!response.ok) {
const err = new Error(`Airtable API error: ${await response.text()}`)
console.error(err)
throw err
}

const data = await response.json()
if (!data.records || data.records.length === 0) return null

const id = data.records[0].id
const email = data.records[0].fields.email
const slackId = data.records[0].fields.slack_id

if (!id || !email || !slackId) return null

return { slackId }
}

export async function getPersonByMagicToken(token: string): Promise<{
id: string
email: string
slackId: string
} | null> {
const baseId = process.env.BASE_ID
const apiKey = process.env.AIRTABLE_API_KEY
const table = 'people'

const url = `https://middleman.hackclub.com/airtable/v0/${baseId}/${table}?filterByFormula={magic_auth_token}='${encodeURIComponent(token)}'`

const response = await fetch(url, {
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'User-Agent': 'highseas.hackclub.com (getPersonByMagicToken)',
},
})

if (!response.ok) {
const err = new Error(`Airtable API error: ${await response.text()}`)
console.error(err)
throw err
}

const data = await response.json()
if (!data.records || data.records.length === 0) return null

const id = data.records[0].id
const email = data.records[0].fields.email
const slackId = data.records[0].fields.slack_id

if (!id || !email || !slackId) return null

return { id, email, slackId }
}

export async function getSelfPersonIdentifier(slackId: string) {
const person = await getSelfPerson(slackId)
return person.fields.identifier
}

export const getPersonTicketBalanceAndTutorialStatutWowThisMethodNameSureIsLongPhew =
async (
slackId: string,
): Promise<{ tickets: number; hasCompletedTutorial: boolean }> => {
const person = await getSelfPerson(slackId)
const tickets = person.fields.settled_tickets as number
const hasCompletedTutorial = person.fields.academy_completed === true

return { tickets, hasCompletedTutorial }
}

// deprecate
export async function getVotesRemainingForNextPendingShip(slackId: string) {
const person = await getSelfPerson(slackId)
return person['fields']['votes_remaining_for_next_pending_ship'] as number
Expand All @@ -174,7 +23,6 @@ export interface SafePerson {
referralLink: string
}

// Good method
export async function safePerson(): Promise<SafePerson> {
const record = await person()

Expand Down
Loading

0 comments on commit 72a43ac

Please sign in to comment.