Fixed auth and regained account access

This commit is contained in:
blaisadmin
2026-04-09 00:04:48 -04:00
parent a7d70cb177
commit 5b0304ee93
2 changed files with 59 additions and 12 deletions
+45 -12
View File
@@ -452,6 +452,37 @@ const ensureSchema = async () => {
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
ALTER TABLE workspace_members
ADD COLUMN IF NOT EXISTS email VARCHAR(255),
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS invite_email VARCHAR(255),
ADD COLUMN IF NOT EXISTS accepted_at TIMESTAMPTZ;
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'workspace_members'
AND column_name = 'email'
) THEN
UPDATE workspace_members
SET invite_email = COALESCE(invite_email, email)
WHERE invite_email IS NULL;
END IF;
END $$;
UPDATE workspace_members
SET invite_email = ''
WHERE invite_email IS NULL;
UPDATE workspace_members
SET email = invite_email
WHERE email IS NULL;
ALTER TABLE workspace_members
ALTER COLUMN invite_email SET NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_members_workspace_email CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_members_workspace_email
ON workspace_members (workspace_id, invite_email); ON workspace_members (workspace_id, invite_email);
@@ -586,7 +617,7 @@ const getWorkspaceById = async (workspaceId: number) => {
const getMembershipForUser = async (userId: string, workspaceId: number) => { const getMembershipForUser = async (userId: string, workspaceId: number) => {
const result = await pool.query<WorkspaceMemberRow>( const result = await pool.query<WorkspaceMemberRow>(
`SELECT id, workspace_id, user_id, invite_email, name, role, accepted_at::text, created_at `SELECT id, workspace_id, user_id, COALESCE(invite_email, email) AS invite_email, name, role, accepted_at::text, created_at
FROM workspace_members FROM workspace_members
WHERE workspace_id = $1 WHERE workspace_id = $1
AND user_id = $2`, AND user_id = $2`,
@@ -611,7 +642,7 @@ const listMembershipsForUser = async (userId: string) => {
workspace_members.id, workspace_members.id,
workspace_members.workspace_id, workspace_members.workspace_id,
workspace_members.user_id, workspace_members.user_id,
workspace_members.invite_email, COALESCE(workspace_members.invite_email, workspace_members.email) AS invite_email,
workspace_members.name, workspace_members.name,
workspace_members.role, workspace_members.role,
workspace_members.accepted_at::text, workspace_members.accepted_at::text,
@@ -691,10 +722,11 @@ const ensurePersonalWorkspaceForUser = async (user: UserRow) => {
} }
await pool.query( await pool.query(
`INSERT INTO workspace_members (workspace_id, user_id, invite_email, name, role, accepted_at) `INSERT INTO workspace_members (workspace_id, user_id, email, invite_email, name, role, accepted_at)
VALUES ($1, $2, $3, $4, 'owner', CURRENT_TIMESTAMP) VALUES ($1, $2, $3, $3, $4, 'owner', CURRENT_TIMESTAMP)
ON CONFLICT (workspace_id, invite_email) DO UPDATE ON CONFLICT (workspace_id, invite_email) DO UPDATE
SET user_id = EXCLUDED.user_id, SET user_id = EXCLUDED.user_id,
email = EXCLUDED.email,
name = EXCLUDED.name, name = EXCLUDED.name,
role = 'owner', role = 'owner',
accepted_at = CURRENT_TIMESTAMP`, accepted_at = CURRENT_TIMESTAMP`,
@@ -709,7 +741,7 @@ const claimWorkspaceInvites = async (user: UserRow) => {
`UPDATE workspace_members `UPDATE workspace_members
SET user_id = $1, SET user_id = $1,
accepted_at = CURRENT_TIMESTAMP accepted_at = CURRENT_TIMESTAMP
WHERE LOWER(invite_email) = LOWER($2) WHERE LOWER(COALESCE(invite_email, email)) = LOWER($2)
AND user_id IS NULL`, AND user_id IS NULL`,
[user.id, user.email], [user.id, user.email],
); );
@@ -862,7 +894,7 @@ const resolveAuth = async (token: string) => {
workspace_members.id AS membership_id_row, workspace_members.id AS membership_id_row,
workspace_members.workspace_id AS membership_workspace_id, workspace_members.workspace_id AS membership_workspace_id,
workspace_members.user_id AS membership_user_id, workspace_members.user_id AS membership_user_id,
workspace_members.invite_email AS membership_invite_email, COALESCE(workspace_members.invite_email, workspace_members.email) AS membership_invite_email,
workspace_members.name AS membership_name, workspace_members.name AS membership_name,
workspace_members.role AS membership_role, workspace_members.role AS membership_role,
workspace_members.accepted_at::text AS membership_accepted_at, workspace_members.accepted_at::text AS membership_accepted_at,
@@ -1390,8 +1422,8 @@ app.post('/api/workspaces', requireAuth, async (req: Request, res: Response, nex
); );
await pool.query( await pool.query(
`INSERT INTO workspace_members (workspace_id, user_id, invite_email, name, role, accepted_at) `INSERT INTO workspace_members (workspace_id, user_id, email, invite_email, name, role, accepted_at)
VALUES ($1, $2, $3, $4, 'owner', CURRENT_TIMESTAMP)`, VALUES ($1, $2, $3, $3, $4, 'owner', CURRENT_TIMESTAMP)`,
[workspaceId, req.auth!.user.id, req.auth!.user.email, req.auth!.user.name], [workspaceId, req.auth!.user.id, req.auth!.user.email, req.auth!.user.name],
); );
@@ -1438,7 +1470,7 @@ app.put('/api/workspace', requireAuth, requireWorkspaceRole(['owner', 'manager']
app.get('/api/workspace/members', requireAuth, async (req: Request, res: Response, next: NextFunction) => { app.get('/api/workspace/members', requireAuth, async (req: Request, res: Response, next: NextFunction) => {
try { try {
const result = await pool.query<WorkspaceMemberRow>( const result = await pool.query<WorkspaceMemberRow>(
`SELECT id, workspace_id, user_id, invite_email, name, role, accepted_at::text, created_at `SELECT id, workspace_id, user_id, COALESCE(invite_email, email) AS invite_email, name, role, accepted_at::text, created_at
FROM workspace_members FROM workspace_members
WHERE workspace_id = $1 WHERE workspace_id = $1
ORDER BY created_at ASC`, ORDER BY created_at ASC`,
@@ -1470,14 +1502,15 @@ app.post('/api/workspace/members', requireAuth, requireWorkspaceRole(['owner', '
const existingUserRow = existingUser.rows[0]; const existingUserRow = existingUser.rows[0];
const result = await pool.query<WorkspaceMemberRow>( const result = await pool.query<WorkspaceMemberRow>(
`INSERT INTO workspace_members (workspace_id, user_id, invite_email, name, role, accepted_at) `INSERT INTO workspace_members (workspace_id, user_id, email, invite_email, name, role, accepted_at)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $3, $4, $5, $6)
ON CONFLICT (workspace_id, invite_email) DO UPDATE ON CONFLICT (workspace_id, invite_email) DO UPDATE
SET name = EXCLUDED.name, SET name = EXCLUDED.name,
role = EXCLUDED.role, role = EXCLUDED.role,
email = EXCLUDED.email,
user_id = COALESCE(workspace_members.user_id, EXCLUDED.user_id), user_id = COALESCE(workspace_members.user_id, EXCLUDED.user_id),
accepted_at = COALESCE(workspace_members.accepted_at, EXCLUDED.accepted_at) accepted_at = COALESCE(workspace_members.accepted_at, EXCLUDED.accepted_at)
RETURNING id, workspace_id, user_id, invite_email, name, role, accepted_at::text, created_at`, RETURNING id, workspace_id, user_id, COALESCE(invite_email, email) AS invite_email, name, role, accepted_at::text, created_at`,
[ [
req.auth!.workspace.id, req.auth!.workspace.id,
existingUserRow?.id ?? null, existingUserRow?.id ?? null,
+14
View File
@@ -28,6 +28,20 @@ services:
POSTGRES_USER: ${POSTGRES_USER:-flockpal} POSTGRES_USER: ${POSTGRES_USER:-flockpal}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-flockpal_dev_password} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-flockpal_dev_password}
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000} FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
BACKEND_URL: ${BACKEND_URL:-http://localhost:5000}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
APPLE_CLIENT_ID: ${APPLE_CLIENT_ID:-}
APPLE_CLIENT_SECRET: ${APPLE_CLIENT_SECRET:-}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_SECURE: ${SMTP_SECURE:-false}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASS: ${SMTP_PASS:-}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
SMTP_FROM_NAME: ${SMTP_FROM_NAME:-FlockPal}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy