Skip to content

Commit

Permalink
fix(prisma-transaction): remove Promise.all in Prisma transaction (#21)
Browse files Browse the repository at this point in the history
* fix(prisma-transaction): remove Promise.all in Prisma transaction

* fix(forward-error): forward 500 error and improve health check
  • Loading branch information
Cali93 authored Feb 7, 2024
1 parent eee3d6f commit 1473561
Show file tree
Hide file tree
Showing 14 changed files with 515 additions and 371 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ DATABASE_URL="postgres://postgres.[YOUR_SUPABASE_PROJECT_ID]:[YOUR_DB_PASSWORD]@
REDIS_PASSWORD="authpassword"
REDIS_HOST=redis
REDIS_PORT=6379
INFURA_API_KEY=
WALLETCONNECT_PROJECT_ID=
4 changes: 2 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
TF_VAR_redis_host: ${{ secrets.REDIS_HOST }}
TF_VAR_redis_port: ${{ secrets.REDIS_PORT }}
TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD }}
TF_VAR_infura_api_key: ${{ secrets.INFURA_API_KEY }}
TF_VAR_walletconnect_project_id: ${{ secrets.WALLETCONNECT_PROJECT_ID }}
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand Down Expand Up @@ -104,7 +104,7 @@ jobs:
TF_VAR_redis_host: ${{ secrets.REDIS_HOST }}
TF_VAR_redis_port: ${{ secrets.REDIS_PORT }}
TF_VAR_redis_password: ${{ secrets.REDIS_PASSWORD }}
TF_VAR_infura_api_key: ${{ secrets.INFURA_API_KEY }}
TF_VAR_walletconnect_project_id: ${{ secrets.WALLETCONNECT_PROJECT_ID }}
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "3"
version: '3'
services:
api:
build: .
Expand All @@ -18,7 +18,7 @@ services:
REDIS_PASSWORD: ${REDIS_PASSWORD}
REDIS_HOST: ${REDIS_HOST}
REDIS_PORT: ${REDIS_PORT}
INFURA_API_KEY: ${INFURA_API_KEY}
WALLETCONNECT_PROJECT_ID: ${WALLETCONNECT_PROJECT_ID}
depends_on:
redis:
condition: service_healthy
Expand All @@ -28,6 +28,6 @@ services:
image: redis:6.2.12
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: ['CMD', 'redis-cli', 'ping']
ports:
- "6379:6379"
- '6379:6379'
464 changes: 294 additions & 170 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@
"author": "",
"license": "ISC",
"dependencies": {
"@prisma/client": "^5.3.1",
"connect-redis": "^7.1.0",
"@prisma/client": "^5.9.1",
"connect-redis": "^7.1.1",
"cookie": "^0.5.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"ethers": "^6.7.1",
"dotenv": "^16.4.1",
"ethers": "^6.10.0",
"express": "^4.18.2",
"express-rate-limit": "^7.0.2",
"express-session": "^1.17.3",
"express-rate-limit": "^7.1.5",
"express-session": "^1.18.0",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2",
"siwe": "^2.1.4"
},
"devDependencies": {
"@types/cookie": "^0.5.2",
"@types/cors": "^2.8.14",
"@types/express": "^4.17.18",
"@types/express-session": "^1.17.8",
"@types/jsonwebtoken": "^9.0.3",
"@types/node": "^18.14.4",
"@types/cookie": "^0.5.4",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/express-session": "^1.17.10",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^18.19.14",
"concurrently": "^7.6.0",
"nodemon": "^3.0.1",
"prisma": "^5.3.1",
"nodemon": "^3.0.3",
"prisma": "^5.9.1",
"typescript": "^4.9.5"
}
}
29 changes: 28 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ model mfa_challenges {
ip_address String @db.Inet
mfa_factors mfa_factors @relation(fields: [factor_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "mfa_challenges_auth_factor_id_fkey")
@@index([created_at(sort: Desc)], map: "mfa_challenge_created_at_idx")
@@schema("auth")
}

Expand All @@ -85,6 +86,7 @@ model mfa_factors {
users users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@index([user_id, created_at], map: "factor_id_created_at_idx")
@@index([user_id])
@@schema("auth")
}

Expand All @@ -104,7 +106,8 @@ model refresh_tokens {
@@index([instance_id, user_id])
@@index([parent])
@@index([session_id, revoked])
@@index([token])
@@index([session_id], map: "refresh_token_session_id")
@@index([updated_at(sort: Desc)])
@@schema("auth")
}

Expand Down Expand Up @@ -132,10 +135,13 @@ model saml_relay_states {
from_ip_address String? @db.Inet
created_at DateTime? @db.Timestamptz(6)
updated_at DateTime? @db.Timestamptz(6)
flow_state_id String? @db.Uuid
flow_state flow_state? @relation(fields: [flow_state_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
sso_providers sso_providers @relation(fields: [sso_provider_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@index([for_email])
@@index([sso_provider_id])
@@index([created_at(sort: Desc)])
@@schema("auth")
}

Expand All @@ -159,6 +165,7 @@ model sessions {
@@index([user_id])
@@index([user_id, created_at], map: "user_id_created_at_idx")
@@index([not_after(sort: Desc)])
@@schema("auth")
}

Expand Down Expand Up @@ -229,6 +236,26 @@ model users {
@@schema("auth")
}

/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
model flow_state {
id String @id @db.Uuid
user_id String? @db.Uuid
auth_code String
code_challenge String
provider_type String
provider_access_token String?
provider_refresh_token String?
created_at DateTime? @db.Timestamptz(6)
updated_at DateTime? @db.Timestamptz(6)
authentication_method String
saml_relay_states saml_relay_states[]
@@index([created_at(sort: Desc)])
@@index([auth_code], map: "idx_auth_code")
@@index([user_id, authentication_method], map: "idx_user_id_auth_method")
@@schema("auth")
}

enum aal_level {
aal1
aal2
Expand Down
80 changes: 38 additions & 42 deletions src/handlers/verify.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,80 @@
import { ethers } from "ethers";
import { Request, Response } from "express";
import { SiweErrorType, SiweMessage } from "siwe";
import { createOrUpdateUser } from "../services/prisma";
import { ethers } from 'ethers'
import { Request, Response } from 'express'
import { SiweErrorType, SiweMessage } from 'siwe'
import { createOrUpdateUser } from '../services/prisma'

const provider = new ethers.InfuraProvider(
"mainnet",
process.env.INFURA_API_KEY
);
const provider = new ethers.JsonRpcProvider(
`https://rpc.walletconnect.com/v1?chainId=eip155:1&projectId=${process.env.WALLETCONNECT_PROJECT_ID}`
)

export const verifyAndSignIn = async (req: Request, res: Response) => {
try {
if (!req.body.message) {
res
.status(422)
.json({ message: "Expected prepareMessage object as body." });
return;
return res.status(422).json({ message: 'Expected prepareMessage object as body.' })
}

const message = new SiweMessage(req.body.message);
const message = new SiweMessage(req.body.message)
const fields = await message.verify(
{
signature: req.body.signature,
nonce: req.session.nonce,
nonce: req.session.nonce
},
{
provider,
provider
}
);
)

req.session.siwe = fields.data;
req.session.siwe = fields.data
if (!fields.data.expirationTime) {
return res.status(422).json({
message: "Expected expirationTime to be set.",
});
message: 'Expected expirationTime to be set.'
})
}
req.session.cookie.expires = new Date(fields.data.expirationTime);
req.session.cookie.expires = new Date(fields.data.expirationTime)

const { accessToken, refreshToken } = await createOrUpdateUser(fields.data);
const { accessToken, refreshToken } = await createOrUpdateUser(fields.data)

return req.session.save(() => {
return res.status(200).json({
accessToken: accessToken,
refreshToken: refreshToken,
});
});
refreshToken: refreshToken
})
})
} catch (e: any) {
console.error(e);
req.session.siwe = undefined;
req.session.nonce = undefined;
console.error(e)
req.session.siwe = undefined
req.session.nonce = undefined
try {
switch (e) {
case SiweErrorType.EXPIRED_MESSAGE: {
req.session.save(() => res.status(440).json({ message: e.message }));
break;
req.session.save(() => res.status(440).json({ message: e.message }))
break
}
case SiweErrorType.INVALID_SIGNATURE: {
req.session.save(() => res.status(422).json({ message: e.message }));
break;
req.session.save(() => res.status(422).json({ message: e.message }))
break
}
case SiweErrorType.INVALID_ADDRESS: {
req.session.save(() => res.status(422).json({ message: e.message }));
break;
req.session.save(() => res.status(422).json({ message: e.message }))
break
}
case SiweErrorType.NONCE_MISMATCH: {
req.session.save(() => res.status(400).json({ message: e.message }));
break;
req.session.save(() => res.status(400).json({ message: e.message }))
break
}
case SiweErrorType.DOMAIN_MISMATCH: {
req.session.save(() => res.status(400).json({ message: e.message }));
break;
req.session.save(() => res.status(400).json({ message: e.message }))
break
}
default: {
req.session.save(() => res.status(500).json({ message: e.message }));
break;
req.session.save(() => res.status(500).json({ message: e.message ?? `${e}` }))
break
}
}
} catch (sessionError) {
console.error(`Failed to save session, ${JSON.stringify(sessionError)}`);
console.error(`Failed to save session, ${JSON.stringify(sessionError)}`)
}

return;
return
}
};
}
13 changes: 11 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Redis } from 'ioredis'
import { SiweMessage, generateNonce } from 'siwe'
import { verifyAndSignIn } from './handlers/verify'
import { captchaVerification } from './middlewares/captchaVerification'
import { PrismaClient } from '@prisma/client'

dotenv.config()

Expand Down Expand Up @@ -35,6 +36,8 @@ if (!REDIS_PASSWORD) {
throw new ReferenceError('REDIS_PASSWORD missing in environment variables')
}

const prismaClient = new PrismaClient()

// Initialize redis client
const redisClient = new Redis({
host: REDIS_HOST ?? 'redis',
Expand Down Expand Up @@ -106,7 +109,13 @@ const limiter = rateLimit({
app.use(limiter)

app.get('/health', async function (req, res) {
return res.status(200).json({ status: 'OK' })
try {
await prismaClient.$queryRaw<number>`SELECT 1;`
return res.status(200).json({ status: 'OK' })
} catch (error) {
console.error('/health -> DB connection failed')
return res.status(500).json({ status: 'NOT OK' })
}
})

app.get('/nonce', async function (req, res) {
Expand Down Expand Up @@ -143,7 +152,7 @@ app.use((req, res, next) => {

// custom error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack)
console.error(err)
return res.status(500).json({ error: 'Something went wrong!' })
})

Expand Down
Loading

0 comments on commit 1473561

Please sign in to comment.