Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17.6
image: postgres:18
env:
POSTGRES_USER: piyaz
POSTGRES_PASSWORD: piyaz
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
name: piyaz-test
services:
db-test:
image: postgres:17.10
image: postgres:18
environment:
POSTGRES_USER: piyaz
POSTGRES_PASSWORD: piyaz
POSTGRES_DB: piyaz_test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/data
- /var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U piyaz -d piyaz_test"]
interval: 2s
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: piyaz
services:
db:
image: postgres:17.10
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: piyaz
Expand All @@ -15,7 +15,7 @@ services:
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- pgdata:/var/lib/postgresql
- ./docker/init-auth.sql:/docker-entrypoint-initdb.d/01-auth.sql:ro
- ./docker/init-rls.sh:/docker-entrypoint-initdb.d/02-rls.sh:ro
- ./docker/grants.sql:/opt/postgres-init/grants.sql:ro
Expand Down
38 changes: 19 additions & 19 deletions docker/grants.sql
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,30 @@ GRANT CREATE ON SCHEMA public TO service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user, service_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user, service_role;

-- neon_auth: app_user reaches it only via SECURITY DEFINER functions.
-- piyaz_auth: app_user reaches it only via SECURITY DEFINER functions.
-- Explicit REVOKEs make re-runs idempotent on pre-lockdown installs.
GRANT USAGE ON SCHEMA neon_auth TO service_role, auth_role;
REVOKE ALL ON SCHEMA neon_auth FROM app_user;
REVOKE ALL ON ALL TABLES IN SCHEMA neon_auth FROM app_user;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA neon_auth FROM app_user;
GRANT USAGE ON SCHEMA piyaz_auth TO service_role, auth_role;
REVOKE ALL ON SCHEMA piyaz_auth FROM app_user;
REVOKE ALL ON ALL TABLES IN SCHEMA piyaz_auth FROM app_user;
REVOKE ALL ON ALL SEQUENCES IN SCHEMA piyaz_auth FROM app_user;

-- service_role: minimal set on neon_auth — used by
-- service_role: minimal set on piyaz_auth — used by
-- clearOrgMembershipArtifacts and the OAuth-session settings UI.
GRANT SELECT, REFERENCES ON neon_auth."member" TO service_role;
GRANT SELECT, REFERENCES ON neon_auth.organization TO service_role;
GRANT SELECT, REFERENCES ON neon_auth."user" TO service_role;
GRANT SELECT, REFERENCES ON neon_auth.invitation TO service_role;
GRANT SELECT, UPDATE ON neon_auth."session" TO service_role;
GRANT SELECT, DELETE ON neon_auth."oauthAccessToken" TO service_role;
GRANT SELECT, REFERENCES ON piyaz_auth."member" TO service_role;
GRANT SELECT, REFERENCES ON piyaz_auth.organization TO service_role;
GRANT SELECT, REFERENCES ON piyaz_auth."user" TO service_role;
GRANT SELECT, REFERENCES ON piyaz_auth.invitation TO service_role;
GRANT SELECT, UPDATE ON piyaz_auth."session" TO service_role;
GRANT SELECT, DELETE ON piyaz_auth."oauthAccessToken" TO service_role;
-- UPDATE: revokeOAuthSession soft-revokes (`revoked = now()`) before
-- cascading the access-token delete in the same tx.
GRANT SELECT, UPDATE, DELETE ON neon_auth."oauthRefreshToken" TO service_role;
GRANT SELECT, DELETE ON neon_auth."oauthConsent" TO service_role;
GRANT SELECT, UPDATE, DELETE ON piyaz_auth."oauthRefreshToken" TO service_role;
GRANT SELECT, DELETE ON piyaz_auth."oauthConsent" TO service_role;
-- SELECT only; writes go through auth_role.
GRANT SELECT ON neon_auth."oauthClient" TO service_role;
GRANT SELECT ON piyaz_auth."oauthClient" TO service_role;

-- auth_role: full DML on neon_auth, zero grants on public. No
-- auth_role: full DML on piyaz_auth, zero grants on public. No
-- ALTER DEFAULT PRIVILEGES — same RLS-race rationale as the public block.
-- New neon_auth tables need explicit grants in their migration.
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA neon_auth TO auth_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA neon_auth TO auth_role;
-- New piyaz_auth tables need explicit grants in their migration.
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA piyaz_auth TO auth_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA piyaz_auth TO auth_role;
8 changes: 4 additions & 4 deletions docker/init-auth.sql
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
-- Neon Auth schema for self-hosted Postgres.
-- Mirrors the tables Neon Auth provisions on hosted Neon projects.
-- Self-managed piyaz_auth schema for Postgres (Better Auth tables).
-- The project does not use Neon Auth Managed; this script owns the schema.
-- Idempotent — safe to re-run on existing databases.

CREATE SCHEMA IF NOT EXISTS neon_auth;
SET search_path TO neon_auth;
CREATE SCHEMA IF NOT EXISTS piyaz_auth;
SET search_path TO piyaz_auth;

CREATE TABLE IF NOT EXISTS "user" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
Expand Down
4 changes: 2 additions & 2 deletions docker/init-pg-cron.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ SELECT cron.schedule(
'purge-oauth-tokens',
'0 3 * * *',
$$
DELETE FROM neon_auth."oauthRefreshToken"
DELETE FROM piyaz_auth."oauthRefreshToken"
WHERE revoked IS NOT NULL OR "expiresAt" < now();
DELETE FROM neon_auth."oauthAccessToken"
DELETE FROM piyaz_auth."oauthAccessToken"
WHERE "expiresAt" < now();
$$
);
72 changes: 36 additions & 36 deletions docker/rls-functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -153,24 +153,24 @@ GRANT EXECUTE ON FUNCTION public.list_org_project_ids(uuid) TO service_role;


-- ---------------------------------------------------------------------------
-- current_user_* helpers — app_user's only path to neon_auth.*.
-- STABLE plpgsql; pinned search_path defeats neon_auth.* shadowing.
-- current_user_* helpers — app_user's only path to piyaz_auth.*.
-- STABLE plpgsql; pinned search_path defeats piyaz_auth.* shadowing.
-- ---------------------------------------------------------------------------

CREATE OR REPLACE FUNCTION public.current_user_org_ids()
RETURNS uuid[]
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN (
SELECT COALESCE(
array_agg("organizationId") FILTER (WHERE "organizationId" IS NOT NULL),
ARRAY[]::uuid[]
)
FROM neon_auth."member"
FROM piyaz_auth."member"
WHERE "userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
);
END;
Expand All @@ -183,13 +183,13 @@ RETURNS text
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
DECLARE
v_role text;
BEGIN
SELECT role INTO v_role
FROM neon_auth."member"
FROM piyaz_auth."member"
WHERE "organizationId" = p_org_id
AND "userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
LIMIT 1;
Expand All @@ -214,7 +214,7 @@ RETURNS TABLE (
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
Expand All @@ -223,11 +223,11 @@ BEGIN
o.name,
o.slug,
m.role,
(SELECT count(*)::int FROM neon_auth."member" mc WHERE mc."organizationId" = o.id) AS member_count,
(SELECT count(*)::int FROM piyaz_auth."member" mc WHERE mc."organizationId" = o.id) AS member_count,
m."createdAt",
o."createdAt"
FROM neon_auth."member" m
INNER JOIN neon_auth."organization" o ON o.id = m."organizationId"
FROM piyaz_auth."member" m
INNER JOIN piyaz_auth."organization" o ON o.id = m."organizationId"
WHERE m."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
ORDER BY m."createdAt" ASC, o.id ASC;
END;
Expand All @@ -240,12 +240,12 @@ RETURNS boolean
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM neon_auth."member"
FROM piyaz_auth."member"
WHERE "userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
);
END;
Expand All @@ -260,16 +260,16 @@ RETURNS TABLE (id uuid, role text, organization_id uuid)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT m.id, m.role, m."organizationId"
FROM neon_auth."member" m
FROM piyaz_auth."member" m
WHERE m.id = p_member_id
AND EXISTS (
SELECT 1
FROM neon_auth."member" caller
FROM piyaz_auth."member" caller
WHERE caller."organizationId" = m."organizationId"
AND caller."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
)
Expand All @@ -284,16 +284,16 @@ RETURNS TABLE (role text)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT m.role
FROM neon_auth."member" m
FROM piyaz_auth."member" m
WHERE m."organizationId" = p_org_id
AND EXISTS (
SELECT 1
FROM neon_auth."member" caller
FROM piyaz_auth."member" caller
WHERE caller."organizationId" = p_org_id
AND caller."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
);
Expand All @@ -317,7 +317,7 @@ RETURNS TABLE (id uuid, name text)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
IF cardinality(p_user_ids) > 1000 THEN
Expand All @@ -326,12 +326,12 @@ BEGIN
END IF;
RETURN QUERY
SELECT u.id, u.name
FROM neon_auth."user" u
FROM piyaz_auth."user" u
WHERE u.id = ANY (p_user_ids)
AND EXISTS (
SELECT 1
FROM neon_auth."member" m1
INNER JOIN neon_auth."member" m2
FROM piyaz_auth."member" m1
INNER JOIN piyaz_auth."member" m2
ON m2."organizationId" = m1."organizationId"
WHERE m1."userId" = u.id
AND m2."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
Expand All @@ -353,19 +353,19 @@ RETURNS TABLE (user_id uuid, name text, email text)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public, neon_auth, pg_catalog, pg_temp
SET search_path = public, piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT ta.user_id, u.name, u.email
FROM public.task_assignees ta
INNER JOIN neon_auth."user" u ON u.id = ta.user_id
INNER JOIN piyaz_auth."user" u ON u.id = ta.user_id
WHERE ta.task_id = p_task_id
AND EXISTS (
SELECT 1
FROM public.tasks t
INNER JOIN public.projects pj ON pj.id = t.project_id
INNER JOIN neon_auth."member" caller
INNER JOIN piyaz_auth."member" caller
ON caller."organizationId" = pj.organization_id
WHERE t.id = p_task_id
AND caller."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
Expand All @@ -386,19 +386,19 @@ RETURNS TABLE (task_id uuid, user_id uuid, name text, email text)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public, neon_auth, pg_catalog, pg_temp
SET search_path = public, piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT ta.task_id, ta.user_id, u.name, u.email
FROM public.tasks t
INNER JOIN public.task_assignees ta ON ta.task_id = t.id
INNER JOIN neon_auth."user" u ON u.id = ta.user_id
INNER JOIN piyaz_auth."user" u ON u.id = ta.user_id
WHERE t.project_id = p_project_id
AND EXISTS (
SELECT 1
FROM public.projects pj
INNER JOIN neon_auth."member" caller
INNER JOIN piyaz_auth."member" caller
ON caller."organizationId" = pj.organization_id
WHERE pj.id = p_project_id
AND caller."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
Expand All @@ -422,17 +422,17 @@ RETURNS TABLE (user_id uuid)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT m."userId"
FROM neon_auth."member" m
FROM piyaz_auth."member" m
WHERE m."organizationId" = p_org_id
AND m."userId" = ANY (p_user_ids)
AND EXISTS (
SELECT 1
FROM neon_auth."member" caller
FROM piyaz_auth."member" caller
WHERE caller."organizationId" = p_org_id
AND caller."userId" = NULLIF(current_setting('app.user_id', TRUE), '')::uuid
);
Expand All @@ -454,13 +454,13 @@ CREATE OR REPLACE FUNCTION public.is_caller_in_invitation_org(
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM neon_auth.invitation i
INNER JOIN neon_auth."member" caller
FROM piyaz_auth.invitation i
INNER JOIN piyaz_auth."member" caller
ON caller."organizationId" = i."organizationId"
WHERE i.id = p_invitation_id
AND i."organizationId" = p_expected_org_id
Expand Down Expand Up @@ -600,11 +600,11 @@ RETURNS TABLE (user_id uuid)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = neon_auth, pg_catalog, pg_temp
SET search_path = piyaz_auth, pg_catalog, pg_temp
AS $$
BEGIN
RETURN QUERY
SELECT m."userId" FROM neon_auth."member" m WHERE m."organizationId" = p_org_id;
SELECT m."userId" FROM piyaz_auth."member" m WHERE m."organizationId" = p_org_id;
END;
$$;
REVOKE EXECUTE ON FUNCTION public.find_org_member_user_ids_as_admin(uuid) FROM public;
Expand Down
10 changes: 5 additions & 5 deletions drizzle/0000_goofy_the_enforcers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,18 @@ CREATE TABLE "team_invite_code" (
CONSTRAINT "team_invite_code_default_role_check" CHECK ("team_invite_code"."default_role" IN ('member', 'admin'))
);
--> statement-breakpoint
ALTER TABLE "projects" ADD CONSTRAINT "projects_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "neon_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "projects" ADD CONSTRAINT "projects_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "piyaz_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_acceptance_criteria" ADD CONSTRAINT "task_acceptance_criteria_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_assignees" ADD CONSTRAINT "task_assignees_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_assignees" ADD CONSTRAINT "task_assignees_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "neon_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_assignees" ADD CONSTRAINT "task_assignees_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "piyaz_auth"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_decisions" ADD CONSTRAINT "task_decisions_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_edges" ADD CONSTRAINT "task_edges_source_task_id_tasks_id_fk" FOREIGN KEY ("source_task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_edges" ADD CONSTRAINT "task_edges_target_task_id_tasks_id_fk" FOREIGN KEY ("target_task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_links" ADD CONSTRAINT "task_links_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_links" ADD CONSTRAINT "task_links_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "neon_auth"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_links" ADD CONSTRAINT "task_links_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "piyaz_auth"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "team_invite_code" ADD CONSTRAINT "team_invite_code_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "neon_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "team_invite_code" ADD CONSTRAINT "team_invite_code_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "neon_auth"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "team_invite_code" ADD CONSTRAINT "team_invite_code_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "piyaz_auth"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "team_invite_code" ADD CONSTRAINT "team_invite_code_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "piyaz_auth"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "projects_organization_id_idx" ON "projects" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "task_acceptance_criteria_task_id_position_idx" ON "task_acceptance_criteria" USING btree ("task_id","position");--> statement-breakpoint
CREATE INDEX "task_assignees_user_id_idx" ON "task_assignees" USING btree ("user_id");--> statement-breakpoint
Expand Down
Loading