(null)
const embedSrc = useMemo(
- () => `${committed.baseUrl.replace(/\/$/, '')}/embed/projects/${committed.projectId}`,
- [committed]
+ () =>
+ `${committed.baseUrl.replace(/\/$/, "")}/embed/projects/${committed.projectId}`,
+ [committed],
)
const expectedOrigin = useMemo(() => originOf(committed.baseUrl), [committed])
@@ -81,7 +87,7 @@ export function App() {
(e: MessageEvent) => {
// Only trust messages coming from the embed's own origin.
if (expectedOrigin && e.origin !== expectedOrigin) return
- if (typeof e.data !== 'string') return
+ if (typeof e.data !== "string") return
let parsed: AuraMessage | undefined
try {
@@ -89,28 +95,36 @@ export function App() {
} catch {
return
}
- if (parsed.app !== 'aura-get-verified') return
+ if (parsed.app !== "aura-get-verified") return
setLog((prev) =>
- [{ ts: new Date().toLocaleTimeString(), origin: e.origin, raw: e.data, parsed }, ...prev].slice(0, 50)
+ [
+ {
+ ts: new Date().toLocaleTimeString(),
+ origin: e.origin,
+ raw: e.data,
+ parsed,
+ },
+ ...prev,
+ ].slice(0, 50),
)
- if (parsed.type === 'verification-success') {
+ if (parsed.type === "verification-success") {
setResult(parsed.data ?? null)
}
},
- [expectedOrigin]
+ [expectedOrigin],
)
useEffect(() => {
- window.addEventListener('message', onMessage)
- return () => window.removeEventListener('message', onMessage)
+ window.addEventListener("message", onMessage)
+ return () => window.removeEventListener("message", onMessage)
}, [onMessage])
const applyConfig = () => {
setResult(null)
setLog([])
- setCommitted({ baseUrl: baseUrl.trim().replace(/\/$/, ''), projectId })
+ setCommitted({ baseUrl: baseUrl.trim().replace(/\/$/, ""), projectId })
setIframeKey((k) => k + 1)
}
@@ -129,9 +143,10 @@ export function App() {
Demo Integration
- A third-party site embedding the Aura verification iframe. Complete the flow in the frame
- below; on success the embed posts the brightId, verification{' '}
- signature, level and score back to this page.
+ A third-party site embedding the Aura verification iframe. Complete
+ the flow in the frame below; on success the embed posts the{" "}
+ brightId, verification signature, level
+ and score back to this page.
@@ -185,7 +200,7 @@ export function App() {
-
+
Result
{result ? (
@@ -195,28 +210,32 @@ export function App() {
BrightID
- {result.brightId ?? '—'}
+ {result.brightId ?? "—"}
{result.brightId && }
Aura level
- {result.auraLevel ?? '—'}
+ {result.auraLevel ?? "—"}
Aura score
- {result.auraScore ?? '—'}
+ {result.auraScore ?? "—"}
Signature
{result.signature ? (
<>
- {JSON.stringify(result.signature, null, 2)}
+
+ {JSON.stringify(result.signature, null, 2)}
+
>
) : (
- no signature (unverified / API failed)
+
+ no signature (unverified / API failed)
+
)}
@@ -236,7 +255,9 @@ export function App() {
{log.length === 0 ? (
📡
-
No aura-get-verified messages yet.
+
+ No aura-get-verified messages yet.
+
) : (
@@ -244,7 +265,7 @@ export function App() {
-
{entry.ts}
{entry.parsed?.type}
diff --git a/apps/interface/api/lib/schema.ts b/apps/interface/api/lib/schema.ts
index 7878241..375c1e6 100644
--- a/apps/interface/api/lib/schema.ts
+++ b/apps/interface/api/lib/schema.ts
@@ -7,6 +7,7 @@ import {
serial,
text,
timestamp,
+ uniqueIndex,
varchar
} from 'drizzle-orm/pg-core'
@@ -88,7 +89,9 @@ export const verificationsTable = pgTable('verifications', {
auraScore: integer(),
auraLevel: integer(),
verifiedAt: timestamp().notNull().defaultNow()
-})
+}, (table) => [
+ uniqueIndex('verifications_user_project_uidx').on(table.userId, table.projectId)
+])
export const brightIdAppsTable = pgTable('brightid_apps', {
key: text('key').primaryKey(), // Unique key
diff --git a/apps/interface/api/projects/[id]/verify.ts b/apps/interface/api/projects/[id]/verify.ts
index 768d405..bba6a9b 100644
--- a/apps/interface/api/projects/[id]/verify.ts
+++ b/apps/interface/api/projects/[id]/verify.ts
@@ -1,3 +1,4 @@
+import { VercelRequest, VercelResponse } from '@vercel/node'
import { and, eq } from 'drizzle-orm'
import { z } from 'zod'
import withCors from '../../lib/cors.js'
@@ -6,15 +7,30 @@ import { projectsTable, verificationsTable } from '../../lib/schema.js'
const verifySchema = z.object({
client: z.string().min(1).max(100),
- auraScore: z.number().int().optional(),
+ auraScore: z.number().optional(),
auraLevel: z.number().int().optional(),
userId: z.string()
})
-async function handler(req: Request, { params }: { params: { id: string } }) {
+async function handler(req: VercelRequest, res: VercelResponse) {
+ const rawId = Array.isArray(req.query['id']) ? req.query['id'][0] : req.query['id']
+ const projectId = Number(rawId)
+
+ let body: z.infer
+ try {
+ body = verifySchema.parse(req.body)
+ if (!Number.isInteger(projectId)) {
+ return res.status(400).json({ error: 'Invalid project id' })
+ }
+ } catch {
+ return res.status(400).json({ error: 'Invalid request' })
+ }
+
+ const log = (msg: string, extra?: unknown) =>
+ console.log(`[verify project=${projectId} user=${body.userId}] ${msg}`, extra ?? '')
+
try {
- const body = verifySchema.parse(await req.json())
- const projectId = Number(params.id)
+ log('start')
const [project] = await db
.select({
@@ -34,7 +50,8 @@ async function handler(req: Request, { params }: { params: { id: string } }) {
.limit(1)
if (!project) {
- return Response.json({ error: 'Invalid project or no tokens' }, { status: 400 })
+ log('project not found or inactive')
+ return res.status(400).json({ error: 'Invalid project or no tokens' })
}
const now = new Date()
@@ -48,61 +65,91 @@ async function handler(req: Request, { params }: { params: { id: string } }) {
.limit(1)
if (alreadyVerified.length > 0) {
- return Response.json({ message: 'Already verified', data: alreadyVerified }, { status: 200 })
+ log('already verified')
+ return res.status(200).json({ message: 'Already verified', data: alreadyVerified })
}
- const res = await fetch(
- `${process.env['VITE_SOME_AURA_BACKEND_URL']}/brightid/v6/verifications/${project.brightIdAppId}/${body.userId}?signed=nacl`
- )
-
- const verification = (await res.json())[0] as {
- verification: string
- unique: true
- appUserId: string
- app: string
- verificationHash: string
- sig: {
- r: string
- s: string
- v: number
- }
- publicKey: string
+ log('fetching brightid verification')
+ let apiRes: Response
+ try {
+ apiRes = await fetch(
+ `${process.env['VITE_SOME_AURA_BACKEND_URL']}/brightid/v6/verifications/${project.brightIdAppId}/${body.userId}?signed=nacl`,
+ { signal: AbortSignal.timeout(10_000) }
+ )
+ } catch (err) {
+ log('brightid fetch failed/timeout', err)
+ return res.status(504).json({ error: 'Verification service unavailable' })
}
+ log(`brightid responded status=${apiRes.status}`)
- if (!verification.unique)
- return Response.json({ error: 'User is not verified' }, { status: 400 })
+ if (!apiRes.ok) {
+ log('brightid non-ok status')
+ return res.status(502).json({ error: 'Verification service error' })
+ }
- await db.transaction(async (tx) => {
- await tx.insert(verificationsTable).values({
- userId: body.userId,
- projectId,
- client: body.client,
- auraScore: body.auraScore,
- auraLevel: body.auraLevel,
- verifiedAt: now,
- signature: JSON.stringify(verification.sig)
- })
+ const verification = (await apiRes.json())[0] as
+ | {
+ verification: string
+ unique: boolean
+ appUserId: string
+ app: string
+ verificationHash: string
+ sig: {
+ r: string
+ s: string
+ v: number
+ }
+ publicKey: string
+ }
+ | undefined
- await tx
- .update(projectsTable)
- .set({ remainingtokens: (project.remainingtokens ?? 0) - 1 })
- .where(eq(projectsTable.id, projectId))
- })
+ if (!verification?.unique) {
+ log('user not unique/verified')
+ return res.status(400).json({ error: 'User is not verified' })
+ }
+
+ try {
+ await db.transaction(async (tx) => {
+ await tx.insert(verificationsTable).values({
+ userId: body.userId,
+ projectId,
+ client: body.client,
+ auraScore: body.auraScore === undefined ? undefined : Math.round(body.auraScore),
+ auraLevel: body.auraLevel,
+ verifiedAt: now,
+ signature: JSON.stringify(verification.sig)
+ })
+
+ await tx
+ .update(projectsTable)
+ .set({ remainingtokens: (project.remainingtokens ?? 0) - 1 })
+ .where(eq(projectsTable.id, projectId))
+ })
+ } catch (err) {
+ // 23505 = unique_violation: concurrent request already verified this user+project
+ if ((err as { code?: string })?.code === '23505') {
+ log('concurrent verify race, already inserted')
+ return res.status(200).json({ message: 'Already verified' })
+ }
+ throw err
+ }
- return Response.json({
+ log('verification success')
+ return res.status(200).json({
message: 'verification success',
data: {
userId: body.userId,
projectId,
client: body.client,
signature: verification.sig,
- auraScore: body.auraScore,
+ auraScore: body.auraScore === undefined ? undefined : Math.round(body.auraScore),
auraLevel: body.auraLevel,
verifiedAt: now
}
})
} catch (error) {
- return Response.json({ error: 'Invalid request' }, { status: 400 })
+ log('unhandled error', error)
+ return res.status(500).json({ error: 'Internal server error' })
}
}
diff --git a/apps/interface/src/utils/apis/index.ts b/apps/interface/src/utils/apis/index.ts
index 53b4f10..0c3ed72 100644
--- a/apps/interface/src/utils/apis/index.ts
+++ b/apps/interface/src/utils/apis/index.ts
@@ -1,12 +1,13 @@
+import { QueryClient } from '@aura/query'
+import createClient from 'openapi-fetch'
import { AURA_NODE_URL_PROXY } from '@/lib/constants/domains'
import type { paths } from '@/lib/schema'
import type { BrightID } from '@/types/brightid'
import type { Project } from '@/types/projects'
-import { QueryClient } from '@aura/query'
-import createClient from 'openapi-fetch'
export const clientAPI = createClient({
- baseUrl: 'https://aura-get-verified.vercel.app/api'
+ // baseUrl: 'https://aura-get-verified.vercel.app/api'
+ baseUrl: 'http://localhost:3000/api'
})
const baseUrl = AURA_NODE_URL_PROXY
@@ -55,12 +56,20 @@ export interface VerifyProjectResult {
*/
export const verifyProject = async (
projectId: number,
- payload: { userId: string; client: string; auraScore?: number; auraLevel?: number }
+ payload: {
+ userId: string
+ client: string
+ auraScore?: number
+ auraLevel?: number
+ }
) => {
- const res = await clientAPI.POST('/projects/{id}/verify' as never, {
- params: { path: { id: String(projectId) } },
- body: payload
- } as never)
+ const res = await clientAPI.POST(
+ '/projects/{id}/verify' as never,
+ {
+ params: { path: { id: String(projectId) } },
+ body: payload
+ } as never
+ )
if ((res as { error?: unknown }).error) {
throw new Error('Failed to generate verification signature')