From afcb2e4662262f03b888d7e82a06a52e3376bc85 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 4 Oct 2025 17:42:11 -0700 Subject: [PATCH 1/5] docker --- .github/workflows/ci.yml | 30 ++------------------------- roles.sql | 45 ---------------------------------------- 2 files changed, 2 insertions(+), 73 deletions(-) delete mode 100644 roles.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96c9276..aa399c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: services: pg_db: - image: pyramation/pgvector:13.3-alpine + image: supabase/postgres env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -67,24 +67,6 @@ jobs: - name: Build run: pnpm -r build - - name: Create roles - run: psql -f roles.sql - env: - PGHOST: pg_db - PGPORT: 5432 - PGUSER: postgres - PGPASSWORD: password - - - name: Seed app_user - run: | - lql admin-users bootstrap --yes - lql admin-users add --test --yes - env: - PGHOST: pg_db - PGPORT: 5432 - PGUSER: postgres - PGPASSWORD: password - test: needs: setup runs-on: ubuntu-latest @@ -107,7 +89,7 @@ jobs: services: pg_db: - image: pyramation/pgvector:13.3-alpine + image: supabase/postgres env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -158,14 +140,6 @@ jobs: - name: Build run: pnpm -r build - - name: Create roles - run: psql -f roles.sql - - - name: Seed app_user - run: | - lql admin-users bootstrap --yes - lql admin-users add --test --yes - - name: Test ${{ matrix.package }} run: cd ./packages/${{ matrix.package }} && pnpm test diff --git a/roles.sql b/roles.sql deleted file mode 100644 index a3e6e57..0000000 --- a/roles.sql +++ /dev/null @@ -1,45 +0,0 @@ --- anon -CREATE ROLE IF NOT EXISTS anon; - -ALTER USER anon WITH NOCREATEDB; - -ALTER USER anon WITH NOSUPERUSER; - -ALTER USER anon WITH NOCREATEROLE; - -ALTER USER anon WITH NOLOGIN; - -ALTER USER anon WITH NOREPLICATION; - -ALTER USER anon WITH NOBYPASSRLS; - --- authenticated -CREATE ROLE IF NOT EXISTS authenticated; - -ALTER USER authenticated WITH NOCREATEDB; - -ALTER USER authenticated WITH NOSUPERUSER; - -ALTER USER authenticated WITH NOCREATEROLE; - -ALTER USER authenticated WITH NOLOGIN; - -ALTER USER authenticated WITH NOREPLICATION; - -ALTER USER authenticated WITH NOBYPASSRLS; - --- service_role -CREATE ROLE IF NOT EXISTS service_role; - -ALTER USER service_role WITH NOCREATEDB; - -ALTER USER service_role WITH NOSUPERUSER; - -ALTER USER service_role WITH NOCREATEROLE; - -ALTER USER service_role WITH NOLOGIN; - -ALTER USER service_role WITH NOREPLICATION; - --- they CAN bypass RLS -ALTER USER service_role WITH BYPASSRLS; \ No newline at end of file From a38759a5fb11ae11c72248600e02209ba68e479d Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 4 Oct 2025 17:44:01 -0700 Subject: [PATCH 2/5] docker --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa399c1..9f66e8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: services: pg_db: - image: supabase/postgres + image: supabase/postgres:17.6.1.013 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -89,7 +89,7 @@ jobs: services: pg_db: - image: supabase/postgres + image: supabase/postgres:17.6.1.013 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password From f7aed04d28fc0e619d6864d07bd162e31f99a69b Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 4 Oct 2025 17:47:26 -0700 Subject: [PATCH 3/5] super --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f66e8a..3cfc5ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: pg_db: image: supabase/postgres:17.6.1.013 env: - POSTGRES_USER: postgres + POSTGRES_USER: supabase_admin POSTGRES_PASSWORD: password options: >- --health-cmd pg_isready @@ -91,7 +91,7 @@ jobs: pg_db: image: supabase/postgres:17.6.1.013 env: - POSTGRES_USER: postgres + POSTGRES_USER: supabase_admin POSTGRES_PASSWORD: password options: >- --health-cmd pg_isready From ecc2aff274610b4f0ea211758e115df846b284e6 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 4 Oct 2025 17:56:13 -0700 Subject: [PATCH 4/5] lock --- .github/workflows/ci.yml | 3 - packages/base32/LICENSE | 22 - packages/base32/Makefile | 6 - packages/base32/README.md | 5 - .../__snapshots__/base32.decode.test.ts.snap | 21 - .../__snapshots__/base32.encode.test.ts.snap | 15 - .../base32/__tests__/base32.decode.test.ts | 95 ---- .../base32/__tests__/base32.encode.test.ts | 137 ----- .../schemas/base32/procedures/decode.sql | 178 ------- .../schemas/base32/procedures/encode.sql | 266 ---------- .../base32/deploy/schemas/base32/schema.sql | 8 - packages/base32/jest.config.js | 15 - packages/base32/launchql-base32.control | 8 - packages/base32/launchql.plan | 7 - packages/base32/package.json | 19 - .../schemas/base32/procedures/decode.sql | 13 - .../schemas/base32/procedures/encode.sql | 18 - .../base32/revert/schemas/base32/schema.sql | 7 - .../base32/sql/launchql-base32--0.4.6.sql | 327 ------------ .../schemas/base32/procedures/decode.sql | 7 - .../schemas/base32/procedures/encode.sql | 7 - .../base32/verify/schemas/base32/schema.sql | 7 - packages/rls-demo/package.json | 3 - packages/totp/LICENSE | 22 - packages/totp/Makefile | 6 - packages/totp/README.md | 5 - .../__tests__/__snapshots__/algo.test.ts.snap | 25 - packages/totp/__tests__/algo.test.ts | 142 ----- packages/totp/__tests__/totp.test.ts | 26 - .../schemas/totp/procedures/generate_totp.sql | 168 ------ .../schemas/totp/procedures/random_base32.sql | 38 -- .../schemas/totp/procedures/urlencode.sql | 43 -- packages/totp/deploy/schemas/totp/schema.sql | 8 - packages/totp/jest.config.js | 15 - packages/totp/launchql-totp.control | 8 - packages/totp/launchql.plan | 8 - packages/totp/package.json | 28 - .../schemas/totp/procedures/generate_totp.sql | 14 - .../schemas/totp/procedures/random_base32.sql | 8 - .../schemas/totp/procedures/urlencode.sql | 7 - packages/totp/revert/schemas/totp/schema.sql | 7 - packages/totp/sql/launchql-totp--0.4.6.sql | 173 ------- .../schemas/totp/procedures/generate_totp.sql | 7 - .../schemas/totp/procedures/random_base32.sql | 7 - .../schemas/totp/procedures/urlencode.sql | 7 - packages/totp/verify/schemas/totp/schema.sql | 7 - packages/verify/LICENSE | 22 - packages/verify/Makefile | 6 - packages/verify/README.md | 5 - packages/verify/__tests__/ext-verify.test.ts | 490 ------------------ .../deploy/procedures/get_entity_from_str.sql | 21 - .../deploy/procedures/get_schema_from_str.sql | 21 - .../verify/deploy/procedures/list_indexes.sql | 29 -- .../deploy/procedures/list_memberships.sql | 31 -- .../deploy/procedures/verify_constraint.sql | 27 - .../deploy/procedures/verify_domain.sql | 32 -- .../deploy/procedures/verify_extension.sql | 24 - .../deploy/procedures/verify_function.sql | 37 -- .../verify/deploy/procedures/verify_index.sql | 23 - .../deploy/procedures/verify_membership.sql | 27 - .../deploy/procedures/verify_policy.sql | 35 -- .../verify/deploy/procedures/verify_role.sql | 24 - .../deploy/procedures/verify_schema.sql | 24 - .../deploy/procedures/verify_security.sql | 33 -- .../verify/deploy/procedures/verify_table.sql | 30 -- .../deploy/procedures/verify_table_grant.sql | 32 -- .../deploy/procedures/verify_trigger.sql | 32 -- .../verify/deploy/procedures/verify_type.sql | 32 -- .../verify/deploy/procedures/verify_view.sql | 30 -- packages/verify/jest.config.js | 15 - packages/verify/launchql-verify.control | 8 - packages/verify/launchql.plan | 24 - packages/verify/package.json | 24 - .../revert/procedures/get_entity_from_str.sql | 7 - .../revert/procedures/get_schema_from_str.sql | 7 - .../verify/revert/procedures/list_indexes.sql | 7 - .../revert/procedures/list_memberships.sql | 7 - .../revert/procedures/verify_constraint.sql | 7 - .../revert/procedures/verify_domain.sql | 7 - .../revert/procedures/verify_extension.sql | 7 - .../revert/procedures/verify_function.sql | 7 - .../verify/revert/procedures/verify_index.sql | 7 - .../revert/procedures/verify_membership.sql | 7 - .../revert/procedures/verify_policy.sql | 7 - .../verify/revert/procedures/verify_role.sql | 7 - .../revert/procedures/verify_schema.sql | 7 - .../revert/procedures/verify_security.sql | 7 - .../verify/revert/procedures/verify_table.sql | 7 - .../revert/procedures/verify_table_grant.sql | 7 - .../revert/procedures/verify_trigger.sql | 7 - .../verify/revert/procedures/verify_type.sql | 7 - .../verify/revert/procedures/verify_view.sql | 7 - .../verify/sql/launchql-verify--0.4.6.sql | 358 ------------- .../verify/procedures/get_entity_from_str.sql | 7 - .../verify/procedures/get_schema_from_str.sql | 7 - .../verify/verify/procedures/list_indexes.sql | 7 - .../verify/procedures/list_memberships.sql | 7 - .../verify/procedures/verify_constraint.sql | 7 - .../verify/procedures/verify_domain.sql | 7 - .../verify/procedures/verify_extension.sql | 7 - .../verify/procedures/verify_function.sql | 7 - .../verify/verify/procedures/verify_index.sql | 7 - .../verify/procedures/verify_membership.sql | 7 - .../verify/procedures/verify_policy.sql | 7 - .../verify/verify/procedures/verify_role.sql | 7 - .../verify/procedures/verify_schema.sql | 7 - .../verify/procedures/verify_security.sql | 7 - .../verify/verify/procedures/verify_table.sql | 7 - .../verify/procedures/verify_table_grant.sql | 7 - .../verify/procedures/verify_trigger.sql | 7 - .../verify/verify/procedures/verify_type.sql | 7 - .../verify/verify/procedures/verify_view.sql | 7 - pnpm-lock.yaml | 33 -- pnpm-workspace.yaml | 8 +- 114 files changed, 1 insertion(+), 3775 deletions(-) delete mode 100644 packages/base32/LICENSE delete mode 100644 packages/base32/Makefile delete mode 100644 packages/base32/README.md delete mode 100644 packages/base32/__tests__/__snapshots__/base32.decode.test.ts.snap delete mode 100644 packages/base32/__tests__/__snapshots__/base32.encode.test.ts.snap delete mode 100644 packages/base32/__tests__/base32.decode.test.ts delete mode 100644 packages/base32/__tests__/base32.encode.test.ts delete mode 100644 packages/base32/deploy/schemas/base32/procedures/decode.sql delete mode 100644 packages/base32/deploy/schemas/base32/procedures/encode.sql delete mode 100644 packages/base32/deploy/schemas/base32/schema.sql delete mode 100644 packages/base32/jest.config.js delete mode 100644 packages/base32/launchql-base32.control delete mode 100644 packages/base32/launchql.plan delete mode 100644 packages/base32/package.json delete mode 100644 packages/base32/revert/schemas/base32/procedures/decode.sql delete mode 100644 packages/base32/revert/schemas/base32/procedures/encode.sql delete mode 100644 packages/base32/revert/schemas/base32/schema.sql delete mode 100644 packages/base32/sql/launchql-base32--0.4.6.sql delete mode 100644 packages/base32/verify/schemas/base32/procedures/decode.sql delete mode 100644 packages/base32/verify/schemas/base32/procedures/encode.sql delete mode 100644 packages/base32/verify/schemas/base32/schema.sql delete mode 100644 packages/totp/LICENSE delete mode 100644 packages/totp/Makefile delete mode 100644 packages/totp/README.md delete mode 100644 packages/totp/__tests__/__snapshots__/algo.test.ts.snap delete mode 100644 packages/totp/__tests__/algo.test.ts delete mode 100644 packages/totp/__tests__/totp.test.ts delete mode 100644 packages/totp/deploy/schemas/totp/procedures/generate_totp.sql delete mode 100644 packages/totp/deploy/schemas/totp/procedures/random_base32.sql delete mode 100644 packages/totp/deploy/schemas/totp/procedures/urlencode.sql delete mode 100644 packages/totp/deploy/schemas/totp/schema.sql delete mode 100644 packages/totp/jest.config.js delete mode 100644 packages/totp/launchql-totp.control delete mode 100644 packages/totp/launchql.plan delete mode 100644 packages/totp/package.json delete mode 100644 packages/totp/revert/schemas/totp/procedures/generate_totp.sql delete mode 100644 packages/totp/revert/schemas/totp/procedures/random_base32.sql delete mode 100644 packages/totp/revert/schemas/totp/procedures/urlencode.sql delete mode 100644 packages/totp/revert/schemas/totp/schema.sql delete mode 100644 packages/totp/sql/launchql-totp--0.4.6.sql delete mode 100644 packages/totp/verify/schemas/totp/procedures/generate_totp.sql delete mode 100644 packages/totp/verify/schemas/totp/procedures/random_base32.sql delete mode 100644 packages/totp/verify/schemas/totp/procedures/urlencode.sql delete mode 100644 packages/totp/verify/schemas/totp/schema.sql delete mode 100644 packages/verify/LICENSE delete mode 100644 packages/verify/Makefile delete mode 100644 packages/verify/README.md delete mode 100644 packages/verify/__tests__/ext-verify.test.ts delete mode 100644 packages/verify/deploy/procedures/get_entity_from_str.sql delete mode 100644 packages/verify/deploy/procedures/get_schema_from_str.sql delete mode 100644 packages/verify/deploy/procedures/list_indexes.sql delete mode 100644 packages/verify/deploy/procedures/list_memberships.sql delete mode 100644 packages/verify/deploy/procedures/verify_constraint.sql delete mode 100644 packages/verify/deploy/procedures/verify_domain.sql delete mode 100644 packages/verify/deploy/procedures/verify_extension.sql delete mode 100644 packages/verify/deploy/procedures/verify_function.sql delete mode 100644 packages/verify/deploy/procedures/verify_index.sql delete mode 100644 packages/verify/deploy/procedures/verify_membership.sql delete mode 100644 packages/verify/deploy/procedures/verify_policy.sql delete mode 100644 packages/verify/deploy/procedures/verify_role.sql delete mode 100644 packages/verify/deploy/procedures/verify_schema.sql delete mode 100644 packages/verify/deploy/procedures/verify_security.sql delete mode 100644 packages/verify/deploy/procedures/verify_table.sql delete mode 100644 packages/verify/deploy/procedures/verify_table_grant.sql delete mode 100644 packages/verify/deploy/procedures/verify_trigger.sql delete mode 100644 packages/verify/deploy/procedures/verify_type.sql delete mode 100644 packages/verify/deploy/procedures/verify_view.sql delete mode 100644 packages/verify/jest.config.js delete mode 100644 packages/verify/launchql-verify.control delete mode 100644 packages/verify/launchql.plan delete mode 100644 packages/verify/package.json delete mode 100644 packages/verify/revert/procedures/get_entity_from_str.sql delete mode 100644 packages/verify/revert/procedures/get_schema_from_str.sql delete mode 100644 packages/verify/revert/procedures/list_indexes.sql delete mode 100644 packages/verify/revert/procedures/list_memberships.sql delete mode 100644 packages/verify/revert/procedures/verify_constraint.sql delete mode 100644 packages/verify/revert/procedures/verify_domain.sql delete mode 100644 packages/verify/revert/procedures/verify_extension.sql delete mode 100644 packages/verify/revert/procedures/verify_function.sql delete mode 100644 packages/verify/revert/procedures/verify_index.sql delete mode 100644 packages/verify/revert/procedures/verify_membership.sql delete mode 100644 packages/verify/revert/procedures/verify_policy.sql delete mode 100644 packages/verify/revert/procedures/verify_role.sql delete mode 100644 packages/verify/revert/procedures/verify_schema.sql delete mode 100644 packages/verify/revert/procedures/verify_security.sql delete mode 100644 packages/verify/revert/procedures/verify_table.sql delete mode 100644 packages/verify/revert/procedures/verify_table_grant.sql delete mode 100644 packages/verify/revert/procedures/verify_trigger.sql delete mode 100644 packages/verify/revert/procedures/verify_type.sql delete mode 100644 packages/verify/revert/procedures/verify_view.sql delete mode 100644 packages/verify/sql/launchql-verify--0.4.6.sql delete mode 100644 packages/verify/verify/procedures/get_entity_from_str.sql delete mode 100644 packages/verify/verify/procedures/get_schema_from_str.sql delete mode 100644 packages/verify/verify/procedures/list_indexes.sql delete mode 100644 packages/verify/verify/procedures/list_memberships.sql delete mode 100644 packages/verify/verify/procedures/verify_constraint.sql delete mode 100644 packages/verify/verify/procedures/verify_domain.sql delete mode 100644 packages/verify/verify/procedures/verify_extension.sql delete mode 100644 packages/verify/verify/procedures/verify_function.sql delete mode 100644 packages/verify/verify/procedures/verify_index.sql delete mode 100644 packages/verify/verify/procedures/verify_membership.sql delete mode 100644 packages/verify/verify/procedures/verify_policy.sql delete mode 100644 packages/verify/verify/procedures/verify_role.sql delete mode 100644 packages/verify/verify/procedures/verify_schema.sql delete mode 100644 packages/verify/verify/procedures/verify_security.sql delete mode 100644 packages/verify/verify/procedures/verify_table.sql delete mode 100644 packages/verify/verify/procedures/verify_table_grant.sql delete mode 100644 packages/verify/verify/procedures/verify_trigger.sql delete mode 100644 packages/verify/verify/procedures/verify_type.sql delete mode 100644 packages/verify/verify/procedures/verify_view.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cfc5ad..43de770 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,9 +76,6 @@ jobs: fail-fast: false matrix: package: - - verify - - base32 - - totp - rls-demo env: diff --git a/packages/base32/LICENSE b/packages/base32/LICENSE deleted file mode 100644 index 04b6583..0000000 --- a/packages/base32/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2025 Dan Lynch -Copyright (c) 2025 Interweb, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/base32/Makefile b/packages/base32/Makefile deleted file mode 100644 index 7e43962..0000000 --- a/packages/base32/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -EXTENSION = launchql-base32 -DATA = sql/launchql-base32--0.4.6.sql - -PG_CONFIG = pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) diff --git a/packages/base32/README.md b/packages/base32/README.md deleted file mode 100644 index a62ee51..0000000 --- a/packages/base32/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @lql-pg/base32 - -Base32 encoding and decoding functions for PostgreSQL. - -Provides Base32 encoding and decoding functionality for PostgreSQL applications, useful for generating human-readable identifiers. diff --git a/packages/base32/__tests__/__snapshots__/base32.decode.test.ts.snap b/packages/base32/__tests__/__snapshots__/base32.decode.test.ts.snap deleted file mode 100644 index 5273eef..0000000 --- a/packages/base32/__tests__/__snapshots__/base32.decode.test.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`base32.decode cases INQXI 1`] = `"Cat"`; - -exports[`base32.decode cases MNUGK3LJON2HE6LJONTXEZLBOQ====== 1`] = `"chemistryisgreat"`; - -exports[`base32.decode cases MY====== 1`] = `"f"`; - -exports[`base32.decode cases MZXQ==== 1`] = `"fo"`; - -exports[`base32.decode cases MZXW6=== 1`] = `"foo"`; - -exports[`base32.decode cases MZXW6YQ= 1`] = `"foob"`; - -exports[`base32.decode cases MZXW6YTB 1`] = `"fooba"`; - -exports[`base32.decode cases MZXW6YTBOI====== 1`] = `"foobar"`; - -exports[`base32.decode cases case: 1 1`] = `""`; - -exports[`base32.decode cases mzxw6ytb 1`] = `"fooba"`; diff --git a/packages/base32/__tests__/__snapshots__/base32.encode.test.ts.snap b/packages/base32/__tests__/__snapshots__/base32.encode.test.ts.snap deleted file mode 100644 index 0d96f7e..0000000 --- a/packages/base32/__tests__/__snapshots__/base32.encode.test.ts.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`base32.encode case: 1 1`] = `""`; - -exports[`base32.encode f 1`] = `"MY======"`; - -exports[`base32.encode fo 1`] = `"MZXQ===="`; - -exports[`base32.encode foo 1`] = `"MZXW6==="`; - -exports[`base32.encode foob 1`] = `"MZXW6YQ="`; - -exports[`base32.encode fooba 1`] = `"MZXW6YTB"`; - -exports[`base32.encode foobar 1`] = `"MZXW6YTBOI======"`; diff --git a/packages/base32/__tests__/base32.decode.test.ts b/packages/base32/__tests__/base32.decode.test.ts deleted file mode 100644 index 13d30b2..0000000 --- a/packages/base32/__tests__/base32.decode.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { getConnections, PgTestClient } from 'pgsql-test'; -import cases from 'jest-in-case'; - -let pg: PgTestClient; -let teardown: () => Promise; - -beforeAll(async () => { - ({ pg, teardown } = await getConnections()); -}); - -afterAll(async () => { - await teardown(); -}); - - -it('base32_to_decimal', async () => { - const { base32_to_decimal } = await pg.one( - `SELECT base32.base32_to_decimal($1::text) AS base32_to_decimal`, - ['INQXI==='] - ); - expect(base32_to_decimal).toEqual(['8', '13', '16', '23', '8', '=', '=', '=']); -}); - -it('decimal_to_chunks', async () => { - const { decimal_to_chunks } = await pg.one( - `SELECT base32.decimal_to_chunks($1::text[]) AS decimal_to_chunks`, - [['8', '13', '16', '23', '8', '=', '=', '=']] - ); - expect(decimal_to_chunks).toEqual([ - '01000', - '01101', - '10000', - '10111', - '01000', - 'xxxxx', - 'xxxxx', - 'xxxxx' - ]); -}); - -it('decode', async () => { - const { decode } = await pg.one( - `SELECT base32.decode($1::text) AS decode`, - ['INQXI'] - ); - expect(decode).toEqual('Cat'); -}); - -it('zero_fill', async () => { - const { zero_fill } = await pg.one( - `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`, - [300, 2] - ); - expect(zero_fill).toBe('75'); -}); - -it('zero_fill (-)', async () => { - const { zero_fill } = await pg.one( - `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`, - [-300, 2] - ); - expect(zero_fill).toBe('1073741749'); -}); - -it('zero_fill (0)', async () => { - const { zero_fill } = await pg.one( - `SELECT base32.zero_fill($1::int, $2::int) AS zero_fill`, - [-300, 0] - ); - expect(zero_fill).toBe('4294966996'); -}); - -cases( - 'base32.decode cases', - async (opts: { name: string; result: string }) => { - const { decode } = await pg.one( - `SELECT base32.decode($1::text) AS decode`, - [opts.name] - ); - expect(decode).toEqual(opts.result); - expect(decode).toMatchSnapshot(); - }, - [ - { result: '', name: '' }, - { result: 'Cat', name: 'INQXI' }, - { result: 'chemistryisgreat', name: 'MNUGK3LJON2HE6LJONTXEZLBOQ======' }, - { result: 'f', name: 'MY======' }, - { result: 'fo', name: 'MZXQ====' }, - { result: 'foo', name: 'MZXW6===' }, - { result: 'foob', name: 'MZXW6YQ=' }, - { result: 'fooba', name: 'MZXW6YTB' }, - { result: 'fooba', name: 'mzxw6ytb' }, - { result: 'foobar', name: 'MZXW6YTBOI======' } - ] -); diff --git a/packages/base32/__tests__/base32.encode.test.ts b/packages/base32/__tests__/base32.encode.test.ts deleted file mode 100644 index c807f53..0000000 --- a/packages/base32/__tests__/base32.encode.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { getConnections, PgTestClient } from 'pgsql-test'; -import cases from 'jest-in-case'; - -let pg: PgTestClient; -let teardown: () => Promise; - -beforeAll(async () => { - ({ pg, teardown } = await getConnections()); -}); - -afterAll(async () => { - await teardown(); -}); - - -it('to_ascii', async () => { - const { to_ascii } = await pg.one( - `SELECT base32.to_ascii($1::text) AS to_ascii`, - ['Cat'] - ); - expect(to_ascii).toEqual([67, 97, 116]); -}); - -it('to_binary', async () => { - const { to_ascii } = await pg.one( - `SELECT base32.to_ascii($1::text) AS to_ascii`, - ['Cat'] - ); - const { to_binary } = await pg.one( - `SELECT base32.to_binary($1::int[]) AS to_binary`, - [to_ascii] - ); - expect(to_binary).toEqual(['01000011', '01100001', '01110100']); -}); - -it('to_groups', async () => { - const { to_groups } = await pg.one( - `SELECT base32.to_groups($1::text[]) AS to_groups`, - [['01000011', '01100001', '01110100']] - ); - expect(to_groups).toEqual([ - '01000011', - '01100001', - '01110100', - 'xxxxxxxx', - 'xxxxxxxx' - ]); -}); - -it('to_chunks', async () => { - const { to_chunks } = await pg.one( - `SELECT base32.to_chunks($1::text[]) AS to_chunks`, - [['01000011', '01100001', '01110100', 'xxxxxxxx', 'xxxxxxxx']] - ); - expect(to_chunks).toEqual([ - '01000', - '01101', - '10000', - '10111', - '0100x', - 'xxxxx', - 'xxxxx', - 'xxxxx' - ]); -}); - -it('fill_chunks', async () => { - const { fill_chunks } = await pg.one( - `SELECT base32.fill_chunks($1::text[]) AS fill_chunks`, - [[ - '01000', - '01101', - '10000', - '10111', - '0100x', - 'xxxxx', - 'xxxxx', - 'xxxxx' - ]] - ); - expect(fill_chunks).toEqual([ - '01000', - '01101', - '10000', - '10111', - '01000', - 'xxxxx', - 'xxxxx', - 'xxxxx' - ]); -}); - -it('to_decimal', async () => { - const { to_decimal } = await pg.one( - `SELECT base32.to_decimal($1::text[]) AS to_decimal`, - [[ - '01000', - '01101', - '10000', - '10111', - '01000', - 'xxxxx', - 'xxxxx', - 'xxxxx' - ]] - ); - expect(to_decimal).toEqual(['8', '13', '16', '23', '8', '=', '=', '=']); -}); - -it('to_base32', async () => { - const { to_base32 } = await pg.one( - `SELECT base32.to_base32($1::text[]) AS to_base32`, - [['8', '13', '16', '23', '8', '=', '=', '=']] - ); - expect(to_base32).toEqual('INQXI==='); -}); - -cases( - 'base32.encode', - async (opts: { name: string; result: string }) => { - const { encode } = await pg.one( - `SELECT base32.encode($1::text) AS encode`, - [opts.name] - ); - expect(encode).toEqual(opts.result); - expect(encode).toMatchSnapshot(); - }, - [ - { name: '', result: '' }, - { name: 'f', result: 'MY======' }, - { name: 'fo', result: 'MZXQ====' }, - { name: 'foo', result: 'MZXW6===' }, - { name: 'foob', result: 'MZXW6YQ=' }, - { name: 'fooba', result: 'MZXW6YTB' }, - { name: 'foobar', result: 'MZXW6YTBOI======' } - ] -); diff --git a/packages/base32/deploy/schemas/base32/procedures/decode.sql b/packages/base32/deploy/schemas/base32/procedures/decode.sql deleted file mode 100644 index c593f4d..0000000 --- a/packages/base32/deploy/schemas/base32/procedures/decode.sql +++ /dev/null @@ -1,178 +0,0 @@ --- Deploy schemas/base32/procedures/decode to pg - --- requires: schemas/base32/schema --- requires: schemas/base32/procedures/encode - -BEGIN; - --- 'I' => '8' -CREATE FUNCTION base32.base32_alphabet_to_decimal( - input text -) returns text as $$ -DECLARE - alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - alpha int; -BEGIN - alpha = position(input in alphabet) - 1; - IF (alpha < 0) THEN - RETURN '='; - END IF; - RETURN alpha::text; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - --- INQXI=== => ['8', '13', '16', '23', '8', '=', '=', '='] -CREATE FUNCTION base32.base32_to_decimal( - input text -) returns text[] as $$ -DECLARE - i int; - output text[]; -BEGIN - input = upper(input); - FOR i IN 1 .. character_length(input) LOOP - output = array_append(output, base32.base32_alphabet_to_decimal(substring(input from i for 1))); - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' STABLE; - --- ['8', '13', '16', '23', '8', '=', '=', '='] --- [ '01000', '01101', '10000', '10111', '01000', 'xxxxx', 'xxxxx', 'xxxxx' ] -CREATE FUNCTION base32.decimal_to_chunks( - input text[] -) returns text[] as $$ -DECLARE - i int; - part text; - output text[]; -BEGIN - FOR i IN 1 .. cardinality(input) LOOP - part = input[i]; - IF (part = '=') THEN - output = array_append(output, 'xxxxx'); - ELSE - output = array_append(output, right(base32.to_binary(part::int), 5)); - END IF; - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' STABLE; - -CREATE FUNCTION base32.base32_alphabet_to_decimal_int( - input text -) returns int as $$ -DECLARE - alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - alpha int; -BEGIN - alpha = position(input in alphabet) - 1; - RETURN alpha; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - --- this emulates the >>> (unsigned right shift) --- https://stackoverflow.com/questions/41134337/unsigned-right-shift-zero-fill-right-shift-in-php-java-javascript-equiv -CREATE FUNCTION base32.zero_fill( - a int, b int -) returns bigint as $$ -DECLARE - bin text; - m int; -BEGIN - - IF (b >= 32 OR b < -32) THEN - m = b/32; - b = b-(m*32); - END IF; - - IF (b < 0) THEN - b = 32 + b; - END IF; - - IF (b = 0) THEN - return ((a>>1)&2147483647)*2::bigint+((a>>b)&1); - END IF; - - IF (a < 0) THEN - a = (a >> 1); - a = a & 2147483647; -- 0x7fffffff - a = a | 1073741824; -- 0x40000000 - a = (a >> (b - 1)); - ELSE - a = (a >> b); - END IF; - - RETURN a; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -CREATE FUNCTION base32.valid( - input text -) returns boolean as $$ -BEGIN - IF (upper(input) ~* '^[A-Z2-7]+=*$') THEN - RETURN true; - END IF; - RETURN false; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -CREATE FUNCTION base32.decode( - input text -) returns text as $$ -DECLARE - i int; - arr int[]; - output text[]; - len int; - num int; - - value int = 0; - index int = 0; - bits int = 0; -BEGIN - len = character_length(input); - IF (len = 0) THEN - RETURN ''; - END IF; - - IF (NOT base32.valid(input)) THEN - RAISE EXCEPTION 'INVALID_BASE32'; - END IF; - - input = replace(input, '=', ''); - input = upper(input); - len = character_length(input); - num = len * 5 / 8; - - select array(select * from generate_series(1,num)) - INTO arr; - - FOR i IN 1 .. len LOOP - value = (value << 5) | base32.base32_alphabet_to_decimal_int(substring(input from i for 1)); - bits = bits + 5; - IF (bits >= 8) THEN - arr[index] = base32.zero_fill(value, (bits - 8)) & 255; -- arr[index] = (value >>> (bits - 8)) & 255; - index = index + 1; - bits = bits - 8; - END IF; - END LOOP; - - len = cardinality(arr); - FOR i IN 0 .. len-2 LOOP - output = array_append(output, chr(arr[i])); - END LOOP; - - RETURN array_to_string(output, ''); -END; -$$ -LANGUAGE 'plpgsql' STABLE; - -COMMIT; \ No newline at end of file diff --git a/packages/base32/deploy/schemas/base32/procedures/encode.sql b/packages/base32/deploy/schemas/base32/procedures/encode.sql deleted file mode 100644 index 7eec6e4..0000000 --- a/packages/base32/deploy/schemas/base32/procedures/encode.sql +++ /dev/null @@ -1,266 +0,0 @@ --- Deploy schemas/base32/procedures/encode to pg - --- requires: schemas/base32/schema - --- https://tools.ietf.org/html/rfc4648 --- https://www.youtube.com/watch?v=Va8FLD-iuTg - -BEGIN; - - -- '01000011' => 67 -CREATE FUNCTION base32.binary_to_int( - input text -) returns int as $$ -DECLARE - i int; - buf text; -BEGIN - buf = 'SELECT B''' || input || '''::int'; - EXECUTE buf INTO i; - RETURN i; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - -- ASCII decimal values Cat => [67,97,116] -CREATE FUNCTION base32.to_ascii( - input text -) returns int[] as $$ -DECLARE - i int; - output int[]; -BEGIN - FOR i IN 1 .. character_length(input) LOOP - output = array_append(output, ascii(substring(input from i for 1))); - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - --- 67 => '01000011' -CREATE FUNCTION base32.to_binary( - input int -) returns text as $$ -DECLARE - i int = 1; - j int = 0; - output char[] = ARRAY['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']; -BEGIN - WHILE i < 256 LOOP - output[8-j] = (CASE WHEN (input & i) > 0 THEN '1' ELSE '0' END)::char; - i = i << 1; - j = j + 1; - END LOOP; - RETURN array_to_string(output, ''); -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - --- [67,97,116] => [01000011, 01100001, 01110100] -CREATE FUNCTION base32.to_binary( - input int[] -) returns text[] as $$ -DECLARE - i int; - output text[]; -BEGIN - FOR i IN 1 .. cardinality(input) LOOP - output = array_append(output, base32.to_binary(input[i])); - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - -- convert an input byte stream into group of 5 bytes - -- if there are less than 5, adding padding - - -- [01000011, 01100001, 01110100, xxxxxxxx, xxxxxxxx] - -CREATE FUNCTION base32.to_groups( - input text[] -) returns text[] as $$ -DECLARE - i int; - output text[]; - len int = cardinality(input); -BEGIN - IF ( len % 5 = 0 ) THEN - RETURN input; - END IF; - FOR i IN 1 .. 5 - (len % 5) LOOP - input = array_append(input, 'xxxxxxxx'); - END LOOP; - RETURN input; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - -- break these into 5 bit chunks (5 * 8 = 40 bits, when we 40/5 = 8 new elements of 5 bits each) - - -- [01000, 01101, 10000, 10111, 0100x, xxxxx, xxxxx, xxxxx] - -CREATE FUNCTION base32.string_nchars(text, integer) -RETURNS text[] AS $$ -SELECT ARRAY(SELECT substring($1 from n for $2) - FROM generate_series(1, length($1), $2) n); -$$ LANGUAGE sql IMMUTABLE; - -CREATE FUNCTION base32.to_chunks( - input text[] -) returns text[] as $$ -DECLARE - i int; - output text[]; - str text; - len int = cardinality(input); -BEGIN - RETURN base32.string_nchars(array_to_string(input, ''), 5); -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - -- if a chunk has a mix of real bits (0|1) and empty (x), replace x with 0 - - -- [01000, 01101, 10000, 10111, 0100x, xxxxx, xxxxx, xxxxx] - -- [01000, 01101, 10000, 10111, 01000, xxxxx, xxxxx, xxxxx] - -CREATE FUNCTION base32.fill_chunks( - input text[] -) returns text[] as $$ -DECLARE - i int; - output text[]; - chunk text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk ~* '[0-1]+') THEN - chunk = replace(chunk, 'x', '0'); - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - -- convert to decimal value - - -- [01000, 01101, 10000, 10111, 01000, xxxxx, xxxxx, xxxxx] - -- [0b01000, 0b01101, 0b10000, 0b10111, 0b01000, xxxxx, xxxxx, xxxxx] - -- [ 8, 13, 16, 23, 8, '=', '=', '=' ] - -CREATE FUNCTION base32.to_decimal( - input text[] -) returns text[] as $$ -DECLARE - i int; - output text[]; - chunk text; - buf text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk ~* '[x]+') THEN - chunk = '='; - ELSE - chunk = base32.binary_to_int(input[i])::text; - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - - --- Table 3: The Base 32 Alphabet - --- 0 A 9 J 18 S 27 3 --- 1 B 10 K 19 T 28 4 --- 2 C 11 L 20 U 29 5 --- 3 D 12 M 21 V 30 6 --- 4 E 13 N 22 W 31 7 --- 5 F 14 O 23 X --- 6 G 15 P 24 Y (pad) = --- 7 H 16 Q 25 Z --- 8 I 17 R 26 2 - -CREATE FUNCTION base32.base32_alphabet( - input int -) returns char as $$ -DECLARE - alphabet text[] = ARRAY[ - 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', - 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', '2', '3', '4', '5', - '6', '7' - ]::text; -BEGIN - RETURN alphabet[input+1]; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - --- [ 8, 13, 16, 23, 8, '=', '=', '=' ] --- [ I, N, Q, X, I, '=', '=', '=' ] - -CREATE FUNCTION base32.to_base32( - input text[] -) returns text as $$ -DECLARE - i int; - output text[]; - chunk text; - buf text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk = '=') THEN - chunk = '='; - ELSE - chunk = base32.base32_alphabet(chunk::int); - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN array_to_string(output, ''); -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -CREATE FUNCTION base32.encode( - input text -) returns text as $$ -BEGIN - IF (character_length(input) = 0) THEN - RETURN ''; - END IF; - - RETURN - base32.to_base32( - base32.to_decimal( - base32.fill_chunks( - base32.to_chunks( - base32.to_groups( - base32.to_binary( - base32.to_ascii( - input - ) - ) - ) - ) - ) - ) - ); -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -COMMIT; diff --git a/packages/base32/deploy/schemas/base32/schema.sql b/packages/base32/deploy/schemas/base32/schema.sql deleted file mode 100644 index cc265d6..0000000 --- a/packages/base32/deploy/schemas/base32/schema.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Deploy schemas/base32/schema to pg - - -BEGIN; - -CREATE SCHEMA base32; - -COMMIT; diff --git a/packages/base32/jest.config.js b/packages/base32/jest.config.js deleted file mode 100644 index e20e7ef..0000000 --- a/packages/base32/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - - // Match both __tests__ and colocated test files - testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], - - // Ignore build artifacts and type declarations - testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'], - modulePathIgnorePatterns: ['/dist/'], - watchPathIgnorePatterns: ['/dist/'], - - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], -}; diff --git a/packages/base32/launchql-base32.control b/packages/base32/launchql-base32.control deleted file mode 100644 index 4f43d6b..0000000 --- a/packages/base32/launchql-base32.control +++ /dev/null @@ -1,8 +0,0 @@ -# launchql-base32 extension -comment = 'launchql-base32 extension' -default_version = '0.4.6' -module_pathname = '$libdir/launchql-base32' -requires = 'pgcrypto,plpgsql,launchql-verify' -relocatable = false -superuser = false - \ No newline at end of file diff --git a/packages/base32/launchql.plan b/packages/base32/launchql.plan deleted file mode 100644 index c504f10..0000000 --- a/packages/base32/launchql.plan +++ /dev/null @@ -1,7 +0,0 @@ -%syntax-version=1.0.0 -%project=launchql-base32 -%uri=launchql-base32 - -schemas/base32/schema 2017-08-11T08:11:51Z skitch # add schemas/base32/schema -schemas/base32/procedures/encode [schemas/base32/schema] 2017-08-11T08:11:51Z skitch # add schemas/base32/procedures/encode -schemas/base32/procedures/decode [schemas/base32/schema schemas/base32/procedures/encode] 2017-08-11T08:11:51Z skitch # add schemas/base32/procedures/decode \ No newline at end of file diff --git a/packages/base32/package.json b/packages/base32/package.json deleted file mode 100644 index 14a7c59..0000000 --- a/packages/base32/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@lql-pg/base32", - "version": "0.4.6", - "description": "Base32 encoding and decoding functions for PostgreSQL", - "publishConfig": { - "access": "public" - }, - "scripts": { - "bundle": "lql package", - "test": "jest", - "test:watch": "jest --watch" - }, - "dependencies": { - "@lql-pg/verify": "workspace:*" - }, - "devDependencies": { - "@launchql/cli": "^4.9.0" - } -} diff --git a/packages/base32/revert/schemas/base32/procedures/decode.sql b/packages/base32/revert/schemas/base32/procedures/decode.sql deleted file mode 100644 index 11da51b..0000000 --- a/packages/base32/revert/schemas/base32/procedures/decode.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Revert schemas/base32/procedures/decode from pg - -BEGIN; - -DROP FUNCTION base32.decode; -DROP FUNCTION base32.valid; -DROP FUNCTION base32.zero_fill; -DROP FUNCTION base32.base32_alphabet_to_decimal_int; -DROP FUNCTION base32.decimal_to_chunks; -DROP FUNCTION base32.base32_to_decimal; -DROP FUNCTION base32.base32_alphabet_to_decimal; - -COMMIT; diff --git a/packages/base32/revert/schemas/base32/procedures/encode.sql b/packages/base32/revert/schemas/base32/procedures/encode.sql deleted file mode 100644 index d029387..0000000 --- a/packages/base32/revert/schemas/base32/procedures/encode.sql +++ /dev/null @@ -1,18 +0,0 @@ --- Revert schemas/base32/procedures/encode from pg - -BEGIN; - -DROP FUNCTION base32.encode; -DROP FUNCTION base32.to_base32; -DROP FUNCTION base32.base32_alphabet; -DROP FUNCTION base32.to_decimal; -DROP FUNCTION base32.fill_chunks; -DROP FUNCTION base32.to_chunks; -DROP FUNCTION base32.string_nchars; -DROP FUNCTION base32.to_groups; -DROP FUNCTION base32.to_binary(int[]); -DROP FUNCTION base32.to_binary(int); -DROP FUNCTION base32.to_ascii; -DROP FUNCTION base32.binary_to_int; - -COMMIT; diff --git a/packages/base32/revert/schemas/base32/schema.sql b/packages/base32/revert/schemas/base32/schema.sql deleted file mode 100644 index a278688..0000000 --- a/packages/base32/revert/schemas/base32/schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/base32/schema from pg - -BEGIN; - -DROP SCHEMA base32 CASCADE; - -COMMIT; diff --git a/packages/base32/sql/launchql-base32--0.4.6.sql b/packages/base32/sql/launchql-base32--0.4.6.sql deleted file mode 100644 index 34797ba..0000000 --- a/packages/base32/sql/launchql-base32--0.4.6.sql +++ /dev/null @@ -1,327 +0,0 @@ -\echo Use "CREATE EXTENSION launchql-base32" to load this file. \quit -CREATE SCHEMA base32; - -CREATE FUNCTION base32.binary_to_int(input text) RETURNS int AS $EOFCODE$ -DECLARE - i int; - buf text; -BEGIN - buf = 'SELECT B''' || input || '''::int'; - EXECUTE buf INTO i; - RETURN i; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_ascii(input text) RETURNS int[] AS $EOFCODE$ -DECLARE - i int; - output int[]; -BEGIN - FOR i IN 1 .. character_length(input) LOOP - output = array_append(output, ascii(substring(input from i for 1))); - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_binary(input int) RETURNS text AS $EOFCODE$ -DECLARE - i int = 1; - j int = 0; - output char[] = ARRAY['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']; -BEGIN - WHILE i < 256 LOOP - output[8-j] = (CASE WHEN (input & i) > 0 THEN '1' ELSE '0' END)::char; - i = i << 1; - j = j + 1; - END LOOP; - RETURN array_to_string(output, ''); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_binary(input int[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; -BEGIN - FOR i IN 1 .. cardinality(input) LOOP - output = array_append(output, base32.to_binary(input[i])); - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_groups(input text[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; - len int = cardinality(input); -BEGIN - IF ( len % 5 = 0 ) THEN - RETURN input; - END IF; - FOR i IN 1 .. 5 - (len % 5) LOOP - input = array_append(input, 'xxxxxxxx'); - END LOOP; - RETURN input; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.string_nchars(text, int) RETURNS text[] AS $EOFCODE$ -SELECT ARRAY(SELECT substring($1 from n for $2) - FROM generate_series(1, length($1), $2) n); -$EOFCODE$ LANGUAGE sql IMMUTABLE; - -CREATE FUNCTION base32.to_chunks(input text[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; - str text; - len int = cardinality(input); -BEGIN - RETURN base32.string_nchars(array_to_string(input, ''), 5); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.fill_chunks(input text[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; - chunk text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk ~* '[0-1]+') THEN - chunk = replace(chunk, 'x', '0'); - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_decimal(input text[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; - chunk text; - buf text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk ~* '[x]+') THEN - chunk = '='; - ELSE - chunk = base32.binary_to_int(input[i])::text; - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.base32_alphabet(input int) RETURNS char(1) AS $EOFCODE$ -DECLARE - alphabet text[] = ARRAY[ - 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', - 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', '2', '3', '4', '5', - '6', '7' - ]::text; -BEGIN - RETURN alphabet[input+1]; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.to_base32(input text[]) RETURNS text AS $EOFCODE$ -DECLARE - i int; - output text[]; - chunk text; - buf text; - len int = cardinality(input); -BEGIN - FOR i IN 1 .. len LOOP - chunk = input[i]; - IF (chunk = '=') THEN - chunk = '='; - ELSE - chunk = base32.base32_alphabet(chunk::int); - END IF; - output = array_append(output, chunk); - END LOOP; - RETURN array_to_string(output, ''); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.encode(input text) RETURNS text AS $EOFCODE$ -BEGIN - IF (character_length(input) = 0) THEN - RETURN ''; - END IF; - - RETURN - base32.to_base32( - base32.to_decimal( - base32.fill_chunks( - base32.to_chunks( - base32.to_groups( - base32.to_binary( - base32.to_ascii( - input - ) - ) - ) - ) - ) - ) - ); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.base32_alphabet_to_decimal(input text) RETURNS text AS $EOFCODE$ -DECLARE - alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - alpha int; -BEGIN - alpha = position(input in alphabet) - 1; - IF (alpha < 0) THEN - RETURN '='; - END IF; - RETURN alpha::text; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.base32_to_decimal(input text) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - output text[]; -BEGIN - input = upper(input); - FOR i IN 1 .. character_length(input) LOOP - output = array_append(output, base32.base32_alphabet_to_decimal(substring(input from i for 1))); - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql STABLE; - -CREATE FUNCTION base32.decimal_to_chunks(input text[]) RETURNS text[] AS $EOFCODE$ -DECLARE - i int; - part text; - output text[]; -BEGIN - FOR i IN 1 .. cardinality(input) LOOP - part = input[i]; - IF (part = '=') THEN - output = array_append(output, 'xxxxx'); - ELSE - output = array_append(output, right(base32.to_binary(part::int), 5)); - END IF; - END LOOP; - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql STABLE; - -CREATE FUNCTION base32.base32_alphabet_to_decimal_int(input text) RETURNS int AS $EOFCODE$ -DECLARE - alphabet text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - alpha int; -BEGIN - alpha = position(input in alphabet) - 1; - RETURN alpha; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.zero_fill(a int, b int) RETURNS bigint AS $EOFCODE$ -DECLARE - bin text; - m int; -BEGIN - - IF (b >= 32 OR b < -32) THEN - m = b/32; - b = b-(m*32); - END IF; - - IF (b < 0) THEN - b = 32 + b; - END IF; - - IF (b = 0) THEN - return ((a>>1)&2147483647)*2::bigint+((a>>b)&1); - END IF; - - IF (a < 0) THEN - a = (a >> 1); - a = a & 2147483647; -- 0x7fffffff - a = a | 1073741824; -- 0x40000000 - a = (a >> (b - 1)); - ELSE - a = (a >> b); - END IF; - - RETURN a; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.valid(input text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF (upper(input) ~* '^[A-Z2-7]+=*$') THEN - RETURN true; - END IF; - RETURN false; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION base32.decode(input text) RETURNS text AS $EOFCODE$ -DECLARE - i int; - arr int[]; - output text[]; - len int; - num int; - - value int = 0; - index int = 0; - bits int = 0; -BEGIN - len = character_length(input); - IF (len = 0) THEN - RETURN ''; - END IF; - - IF (NOT base32.valid(input)) THEN - RAISE EXCEPTION 'INVALID_BASE32'; - END IF; - - input = replace(input, '=', ''); - input = upper(input); - len = character_length(input); - num = len * 5 / 8; - - select array(select * from generate_series(1,num)) - INTO arr; - - FOR i IN 1 .. len LOOP - value = (value << 5) | base32.base32_alphabet_to_decimal_int(substring(input from i for 1)); - bits = bits + 5; - IF (bits >= 8) THEN - arr[index] = base32.zero_fill(value, (bits - 8)) & 255; -- arr[index] = (value >>> (bits - 8)) & 255; - index = index + 1; - bits = bits - 8; - END IF; - END LOOP; - - len = cardinality(arr); - FOR i IN 0 .. len-2 LOOP - output = array_append(output, chr(arr[i])); - END LOOP; - - RETURN array_to_string(output, ''); -END; -$EOFCODE$ LANGUAGE plpgsql STABLE; \ No newline at end of file diff --git a/packages/base32/verify/schemas/base32/procedures/decode.sql b/packages/base32/verify/schemas/base32/procedures/decode.sql deleted file mode 100644 index fb03d98..0000000 --- a/packages/base32/verify/schemas/base32/procedures/decode.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/base32/procedures/decode on pg - -BEGIN; - -SELECT verify_function ('base32.decode'); - -ROLLBACK; diff --git a/packages/base32/verify/schemas/base32/procedures/encode.sql b/packages/base32/verify/schemas/base32/procedures/encode.sql deleted file mode 100644 index 53de63f..0000000 --- a/packages/base32/verify/schemas/base32/procedures/encode.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/base32/procedures/encode on pg - -BEGIN; - -SELECT verify_function ('base32.encode'); - -ROLLBACK; diff --git a/packages/base32/verify/schemas/base32/schema.sql b/packages/base32/verify/schemas/base32/schema.sql deleted file mode 100644 index 5cc71cd..0000000 --- a/packages/base32/verify/schemas/base32/schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/base32/schema on pg - -BEGIN; - -SELECT verify_schema ('base32'); - -ROLLBACK; diff --git a/packages/rls-demo/package.json b/packages/rls-demo/package.json index 245eec9..aab79ae 100644 --- a/packages/rls-demo/package.json +++ b/packages/rls-demo/package.json @@ -10,9 +10,6 @@ "test": "jest", "test:watch": "jest --watch" }, - "dependencies": { - "@lql-pg/verify": "workspace:*" - }, "devDependencies": { "@launchql/cli": "^4.9.0" } diff --git a/packages/totp/LICENSE b/packages/totp/LICENSE deleted file mode 100644 index 04b6583..0000000 --- a/packages/totp/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2025 Dan Lynch -Copyright (c) 2025 Interweb, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/totp/Makefile b/packages/totp/Makefile deleted file mode 100644 index 91bce46..0000000 --- a/packages/totp/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -EXTENSION = launchql-totp -DATA = sql/launchql-totp--0.4.6.sql - -PG_CONFIG = pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) diff --git a/packages/totp/README.md b/packages/totp/README.md deleted file mode 100644 index 1d2de6b..0000000 --- a/packages/totp/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @lql-pg/totp - -Time-based One-Time Password (TOTP) authentication. - -Provides TOTP generation, validation, and two-factor authentication functionality for PostgreSQL applications. diff --git a/packages/totp/__tests__/__snapshots__/algo.test.ts.snap b/packages/totp/__tests__/__snapshots__/algo.test.ts.snap deleted file mode 100644 index 15806d0..0000000 --- a/packages/totp/__tests__/__snapshots__/algo.test.ts.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`issue case: 1 1`] = `"476240"`; - -exports[`issue case: 2 1`] = `"788648"`; - -exports[`issue case: 3 1`] = `"080176"`; - -exports[`rfc6238 case: 1 1`] = `"94287082"`; - -exports[`rfc6238 case: 2 1`] = `"07081804"`; - -exports[`rfc6238 case: 3 1`] = `"14050471"`; - -exports[`rfc6238 case: 4 1`] = `"89005924"`; - -exports[`rfc6238 case: 5 1`] = `"69279037"`; - -exports[`rfc6238 case: 6 1`] = `"65353130"`; - -exports[`speakeasy test case: 1 1`] = `"287082"`; - -exports[`speakeasy test case: 2 1`] = `"081804"`; - -exports[`speakeasy test case: 3 1`] = `"360094"`; diff --git a/packages/totp/__tests__/algo.test.ts b/packages/totp/__tests__/algo.test.ts deleted file mode 100644 index 44e3a97..0000000 --- a/packages/totp/__tests__/algo.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { getConnections, PgTestClient } from 'pgsql-test'; -import cases from 'jest-in-case'; - -let pg: PgTestClient; -let teardown: () => Promise; - -beforeAll(async () => { - ({ pg, teardown } = await getConnections()); -}); - -afterAll(async () => { - await teardown(); -}); - - -cases( - 'rfc6238', - async (opts: { date: string; len: number; algo: string; result: string }) => { - const { generate } = await pg.one( - `SELECT totp.generate( - secret := $1, - period := 30, - digits := $2, - time_from := $3, - hash := $4, - encoding := NULL - )`, - ['12345678901234567890', opts.len, opts.date, opts.algo] - ); - expect(generate).toEqual(opts.result); - expect(generate).toMatchSnapshot(); - }, - [ - { date: '1970-01-01 00:00:59', len: 8, algo: 'sha1', result: '94287082' }, - { date: '2005-03-18 01:58:29', len: 8, algo: 'sha1', result: '07081804' }, - { date: '2005-03-18 01:58:31', len: 8, algo: 'sha1', result: '14050471' }, - { date: '2009-02-13 23:31:30', len: 8, algo: 'sha1', result: '89005924' }, - { date: '2033-05-18 03:33:20', len: 8, algo: 'sha1', result: '69279037' }, - { date: '2603-10-11 11:33:20', len: 8, algo: 'sha1', result: '65353130' } - ] -); - -cases( - 'speakeasy test', - async (opts: { date: string; len: number; algo: string; step: number; result: string }) => { - const { generate } = await pg.one( - `SELECT totp.generate( - secret := $1, - period := $5, - digits := $2, - time_from := $3, - hash := $4, - encoding := NULL - )`, - ['12345678901234567890', opts.len, opts.date, opts.algo, opts.step] - ); - expect(generate).toEqual(opts.result); - expect(generate).toMatchSnapshot(); - }, - [ - { date: '1970-01-01 00:00:59', len: 6, step: 30, algo: 'sha1', result: '287082' }, - { date: '2005-03-18 01:58:29', len: 6, step: 30, algo: 'sha1', result: '081804' }, - { date: '2005-03-18 01:58:29', len: 6, step: 60, algo: 'sha1', result: '360094' } - ] -); - -cases( - 'verify', - async (opts: { date: string; len: number; algo?: string; step: number; result: string }) => { - const [{ verified }] = await pg.any( - `SELECT * FROM totp.verify( - secret := $1, - check_totp := $2, - period := $3, - digits := $4, - time_from := $5, - encoding := NULL - ) as verified`, - ['12345678901234567890', opts.result, opts.step, opts.len, opts.date] - ); - expect(verified).toBe(true); - }, - [ - { date: '1970-01-01 00:00:59', len: 6, step: 30, algo: 'sha1', result: '287082' }, - { date: '2005-03-18 01:58:29', len: 6, step: 30, algo: 'sha1', result: '081804' }, - { date: '2005-03-18 01:58:29', len: 6, step: 60, algo: 'sha1', result: '360094' }, - { date: '1970-01-01 00:00:59', len: 8, step: 30, algo: 'sha1', result: '94287082' }, - { date: '2005-03-18 01:58:29', len: 8, step: 30, algo: 'sha1', result: '07081804' }, - { date: '2005-03-18 01:58:31', len: 8, step: 30, algo: 'sha1', result: '14050471' }, - { date: '2009-02-13 23:31:30', len: 8, step: 30, algo: 'sha1', result: '89005924' }, - { date: '2033-05-18 03:33:20', len: 8, algo: 'sha1', step: 30, result: '69279037' }, - { date: '2603-10-11 11:33:20', len: 8, algo: 'sha1', step: 30, result: '65353130' } - ] -); - -cases( - 'issue', - async (opts: { encoding: string | null; secret: string; date: string; len: number; step: number; algo: string; result: string }) => { - const { generate } = await pg.one( - `SELECT totp.generate( - secret := $1, - period := $2, - digits := $3, - time_from := $4, - hash := $5, - encoding := $6 - )`, - [opts.secret, opts.step, opts.len, opts.date, opts.algo, opts.encoding] - ); - expect(generate).toEqual(opts.result); - expect(generate).toMatchSnapshot(); - }, - [ - { - encoding: null, - secret: 'OH3NUPO3WOGOZZQ4', - date: '2020-11-14 07:46:37.212048+00', - len: 6, - step: 30, - algo: 'sha1', - result: '476240' - }, - { - encoding: 'base32', - secret: 'OH3NUPO3WOGOZZQ4', - date: '2020-11-14 07:46:37.212048+00', - len: 6, - step: 30, - algo: 'sha1', - result: '788648' - }, - { - encoding: 'base32', - secret: 'OH3NUPO', - date: '2020-11-14 07:46:37.212048+00', - len: 6, - step: 30, - algo: 'sha1', - result: '080176' - } - ] -); diff --git a/packages/totp/__tests__/totp.test.ts b/packages/totp/__tests__/totp.test.ts deleted file mode 100644 index 460ce21..0000000 --- a/packages/totp/__tests__/totp.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getConnections, PgTestClient } from 'pgsql-test'; - -let pg: PgTestClient; -let teardown: () => Promise; - -beforeAll(async () => { - ({ pg, teardown } = await getConnections()); -}); - -afterAll(async () => { - await teardown(); -}); - - -it('totp.generate + totp.verify basic', async () => { - const { generate } = await pg.one( - `SELECT totp.generate($1::text) AS generate`, - ['secret'] - ); - const { verify } = await pg.one( - `SELECT totp.verify($1::text, $2::text) AS verify`, - ['secret', generate] - ); - expect(typeof generate).toBe('string'); - expect(verify).toBe(true); -}); diff --git a/packages/totp/deploy/schemas/totp/procedures/generate_totp.sql b/packages/totp/deploy/schemas/totp/procedures/generate_totp.sql deleted file mode 100644 index 668f998..0000000 --- a/packages/totp/deploy/schemas/totp/procedures/generate_totp.sql +++ /dev/null @@ -1,168 +0,0 @@ --- Deploy schemas/totp/procedures/generate_totp to pg --- requires: schemas/totp/schema --- requires: schemas/totp/procedures/urlencode - -BEGIN; - --- https://www.youtube.com/watch?v=VOYxF12K1vE --- https://tools.ietf.org/html/rfc6238 --- http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/ --- https://gist.github.com/bwbroersma/676d0de32263ed554584ab132434ebd9 - -CREATE FUNCTION totp.pad_secret ( - input bytea, - len int -) returns bytea as $$ -DECLARE - output bytea; - orig_length int = octet_length(input); -BEGIN - IF (orig_length = len) THEN - RETURN input; - END IF; - - -- create blank bytea size of new length - output = lpad('', len, 'x')::bytea; - - FOR i IN 0 .. len-1 LOOP - output = set_byte(output, i, get_byte(input, i % orig_length)); - END LOOP; - - RETURN output; -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -CREATE FUNCTION totp.base32_to_hex ( - input text -) returns text as $$ -DECLARE - output text[]; - decoded text = base32.decode(input); - len int = character_length(decoded); - hx text; -BEGIN - - FOR i IN 1 .. len LOOP - hx = to_hex(ascii(substring(decoded from i for 1)))::text; - IF (character_length(hx) = 1) THEN - -- if it is odd number of digits, pad a 0 so it can later - hx = '0' || hx; - END IF; - output = array_append(output, hx); - END LOOP; - - RETURN array_to_string(output, ''); -END; -$$ -LANGUAGE 'plpgsql' IMMUTABLE; - -CREATE FUNCTION totp.hotp(key BYTEA, c INT, digits INT DEFAULT 6, hash TEXT DEFAULT 'sha1') RETURNS TEXT AS $$ -DECLARE - c BYTEA := '\x' || LPAD(TO_HEX(c), 16, '0'); - mac BYTEA := HMAC(c, key, hash); - trunc_offset INT := GET_BYTE(mac, length(mac) - 1) % 16; - result TEXT := SUBSTRING(SET_BIT(SUBSTRING(mac FROM 1 + trunc_offset FOR 4), 7, 0)::TEXT, 2)::BIT(32)::INT % (10 ^ digits)::INT; -BEGIN - RETURN LPAD(result, digits, '0'); -END; -$$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION totp.generate( - secret text, - period int DEFAULT 30, - digits int DEFAULT 6, - time_from timestamptz DEFAULT NOW(), - hash text DEFAULT 'sha1', - encoding text DEFAULT 'base32', - clock_offset int DEFAULT 0 -) RETURNS text AS $$ -DECLARE - c int := FLOOR(EXTRACT(EPOCH FROM time_from) / period)::int + clock_offset; - key bytea; -BEGIN - - IF (encoding = 'base32') THEN - key = ( '\x' || totp.base32_to_hex(secret) )::bytea; - ELSE - key = secret::bytea; - END IF; - - RETURN totp.hotp(key, c, digits, hash); -END; -$$ LANGUAGE plpgsql STABLE; - --- Mitigate timing attacks by using constant-time comparison. --- Mitigates timing attacks by avoiding early-exit and content-dependent work; compares full byte sequences in a length-oblivious loop. --- Context: HN discussion on TOTP '=' comparison timing leaks: https://news.ycombinator.com/item?id=26260667 - --- Context: https://news.ycombinator.com/item?id=26260667 - -CREATE FUNCTION totp.timing_safe_equals(a bytea, b bytea) -RETURNS boolean -AS $$ -DECLARE - la int := length(a); - lb int := length(b); - maxlen int := GREATEST(la, lb); - i int; - diff int := la # lb; - ca int; - cb int; -BEGIN - FOR i IN 0..(maxlen - 1) LOOP - ca := CASE WHEN i < la THEN get_byte(a, i) ELSE 0 END; - cb := CASE WHEN i < lb THEN get_byte(b, i) ELSE 0 END; - diff := diff | (ca # cb); - END LOOP; - RETURN diff = 0; -END; -$$ LANGUAGE plpgsql IMMUTABLE STRICT; - -CREATE FUNCTION totp.timing_safe_equals(a text, b text) -RETURNS boolean -AS $$ --- Verify uses timing-safe equality to avoid leaking mismatch position via timing; do not use direct '=' here. --- See HN discussion for background: https://news.ycombinator.com/item?id=26260667 - - SELECT totp.timing_safe_equals(convert_to(a, 'UTF8'), convert_to(b, 'UTF8')); -$$ LANGUAGE sql IMMUTABLE STRICT; - -CREATE FUNCTION totp.verify ( - secret text, - check_totp text, - period int default 30, - digits int default 6, - time_from timestamptz DEFAULT NOW(), - hash text default 'sha1', - encoding text DEFAULT 'base32', - clock_offset int default 0 -) - RETURNS boolean - AS $$ - SELECT totp.timing_safe_equals( - totp.generate( - secret, - period, - digits, - time_from, - hash, - encoding, - clock_offset - ), - check_totp - ); -$$ -LANGUAGE 'sql'; - -CREATE FUNCTION totp.url (email text, totp_secret text, totp_interval int, totp_issuer text) - RETURNS text - AS $$ - SELECT - concat('otpauth://totp/', totp.urlencode (email), '?secret=', totp.urlencode (totp_secret), '&period=', totp.urlencode (totp_interval::text), '&issuer=', totp.urlencode (totp_issuer)); -$$ -LANGUAGE 'sql' -STRICT IMMUTABLE; - -COMMIT; - diff --git a/packages/totp/deploy/schemas/totp/procedures/random_base32.sql b/packages/totp/deploy/schemas/totp/procedures/random_base32.sql deleted file mode 100644 index 85106c7..0000000 --- a/packages/totp/deploy/schemas/totp/procedures/random_base32.sql +++ /dev/null @@ -1,38 +0,0 @@ --- Uses pgcrypto's gen_random_bytes for cryptographically secure randomness; random() is not suitable for secrets. Preserves RFC 4648 base32 alphabet output. - --- Deploy schemas/totp/procedures/random_base32 to pg --- requires: schemas/totp/schema - -BEGIN; - -CREATE FUNCTION totp.random_base32 (_length int DEFAULT 20) - RETURNS text - LANGUAGE sql - AS $$ - SELECT - string_agg( - ('{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,2,3,4,5,6,7}'::text[]) - [ (get_byte(b, i) % 32) + 1 ], - '' - ) - FROM (SELECT gen_random_bytes(_length) AS b) t, - LATERAL generate_series(0, _length - 1) g(i); -$$; - -CREATE FUNCTION totp.generate_secret(hash TEXT DEFAULT 'sha1') RETURNS BYTEA AS $$ -BEGIN - -- See https://tools.ietf.org/html/rfc4868#section-2.1.2 - -- The optimal key length for HMAC is the block size of the algorithm - CASE - WHEN hash = 'sha1' THEN RETURN totp.random_base32(20); -- = 160 bits - WHEN hash = 'sha256' THEN RETURN totp.random_base32(32); -- = 256 bits - WHEN hash = 'sha512' THEN RETURN totp.random_base32(64); -- = 512 bits - ELSE - RAISE EXCEPTION 'Unsupported hash algorithm for OTP (see RFC6238/4226).'; - RETURN NULL; - END CASE; -END; -$$ LANGUAGE plpgsql VOLATILE; - -COMMIT; - diff --git a/packages/totp/deploy/schemas/totp/procedures/urlencode.sql b/packages/totp/deploy/schemas/totp/procedures/urlencode.sql deleted file mode 100644 index 975f925..0000000 --- a/packages/totp/deploy/schemas/totp/procedures/urlencode.sql +++ /dev/null @@ -1,43 +0,0 @@ --- Deploy schemas/totp/procedures/urlencode to pg --- requires: schemas/totp/schema - --- https://stackoverflow.com/questions/10318014/javascript-encodeuri-like-function-in-postgresql/40762846 -BEGIN; -CREATE FUNCTION totp.urlencode (in_str text) - RETURNS text - AS $$ -DECLARE - _i int4; - _temp varchar; - _ascii int4; - _result text := ''; -BEGIN - FOR _i IN 1..length(in_str) - LOOP - _temp := substr(in_str, _i, 1); - IF _temp ~ '[0-9a-zA-Z:/@._?#-]+' THEN - _result := _result || _temp; - ELSE - _ascii := ascii(_temp); - IF _ascii > x'07ff'::int4 THEN - RAISE exception 'won''t deal with 3 (or more) byte sequences.'; - END IF; - IF _ascii <= x'07f'::int4 THEN - _temp := '%' || to_hex(_ascii); - ELSE - _temp := '%' || to_hex((_ascii & x'03f'::int4) + x'80'::int4); - _ascii := _ascii >> 6; - _temp := '%' || to_hex((_ascii & x'01f'::int4) + x'c0'::int4) || _temp; - END IF; - _result := _result || upper(_temp); - END IF; - END LOOP; - RETURN _result; -END; -$$ -LANGUAGE 'plpgsql' -STRICT IMMUTABLE -; - -COMMIT; - diff --git a/packages/totp/deploy/schemas/totp/schema.sql b/packages/totp/deploy/schemas/totp/schema.sql deleted file mode 100644 index 49da21e..0000000 --- a/packages/totp/deploy/schemas/totp/schema.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Deploy schemas/totp/schema to pg - - -BEGIN; - -CREATE SCHEMA totp; - -COMMIT; diff --git a/packages/totp/jest.config.js b/packages/totp/jest.config.js deleted file mode 100644 index e20e7ef..0000000 --- a/packages/totp/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - - // Match both __tests__ and colocated test files - testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], - - // Ignore build artifacts and type declarations - testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'], - modulePathIgnorePatterns: ['/dist/'], - watchPathIgnorePatterns: ['/dist/'], - - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], -}; diff --git a/packages/totp/launchql-totp.control b/packages/totp/launchql-totp.control deleted file mode 100644 index 72272f2..0000000 --- a/packages/totp/launchql-totp.control +++ /dev/null @@ -1,8 +0,0 @@ -# launchql-totp extension -comment = 'launchql-totp extension' -default_version = '0.4.6' -module_pathname = '$libdir/launchql-totp' -requires = 'pgcrypto,plpgsql,launchql-base32,launchql-verify' -relocatable = false -superuser = false - \ No newline at end of file diff --git a/packages/totp/launchql.plan b/packages/totp/launchql.plan deleted file mode 100644 index 7df9918..0000000 --- a/packages/totp/launchql.plan +++ /dev/null @@ -1,8 +0,0 @@ -%syntax-version=1.0.0 -%project=launchql-totp -%uri=launchql-totp - -schemas/totp/schema 2017-08-11T08:11:51Z skitch # add schemas/totp/schema -schemas/totp/procedures/urlencode [schemas/totp/schema] 2017-08-11T08:11:51Z skitch # add schemas/totp/procedures/urlencode -schemas/totp/procedures/generate_totp [schemas/totp/schema schemas/totp/procedures/urlencode] 2017-08-11T08:11:51Z skitch # add schemas/totp/procedures/generate_totp -schemas/totp/procedures/random_base32 [schemas/totp/schema] 2017-08-11T08:11:51Z skitch # add schemas/totp/procedures/random_base32 \ No newline at end of file diff --git a/packages/totp/package.json b/packages/totp/package.json deleted file mode 100644 index 48addca..0000000 --- a/packages/totp/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@lql-pg/totp", - "version": "0.4.6", - "description": "Time-based One-Time Password (TOTP) authentication", - "publishConfig": { - "access": "public" - }, - "scripts": { - "bundle": "lql package", - "test": "jest", - "test:watch": "jest --watch" - }, - "dependencies": { - "@lql-pg/base32": "workspace:*", - "@lql-pg/verify": "workspace:*" - }, - "devDependencies": { - "@launchql/cli": "^4.9.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/launchql/extensions" - }, - "homepage": "https://github.com/launchql/extensions", - "bugs": { - "url": "https://github.com/launchql/extensions/issues" - } -} diff --git a/packages/totp/revert/schemas/totp/procedures/generate_totp.sql b/packages/totp/revert/schemas/totp/procedures/generate_totp.sql deleted file mode 100644 index 10261e5..0000000 --- a/packages/totp/revert/schemas/totp/procedures/generate_totp.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Revert schemas/totp/procedures/generate_totp from pg - -BEGIN; - -DROP FUNCTION totp.url; -DROP FUNCTION totp.verify; -DROP FUNCTION totp.timing_safe_equals(a text, b text); -DROP FUNCTION totp.timing_safe_equals(a bytea, b bytea); -DROP FUNCTION totp.generate; -DROP FUNCTION totp.hotp; -DROP FUNCTION totp.base32_to_hex; -DROP FUNCTION totp.pad_secret; - -COMMIT; diff --git a/packages/totp/revert/schemas/totp/procedures/random_base32.sql b/packages/totp/revert/schemas/totp/procedures/random_base32.sql deleted file mode 100644 index 22fcac9..0000000 --- a/packages/totp/revert/schemas/totp/procedures/random_base32.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Revert schemas/totp/procedures/random_base32 from pg - -BEGIN; - -DROP FUNCTION totp.generate_secret; -DROP FUNCTION totp.random_base32; - -COMMIT; diff --git a/packages/totp/revert/schemas/totp/procedures/urlencode.sql b/packages/totp/revert/schemas/totp/procedures/urlencode.sql deleted file mode 100644 index 5fd2c85..0000000 --- a/packages/totp/revert/schemas/totp/procedures/urlencode.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/totp/procedures/urlencode from pg - -BEGIN; - -DROP FUNCTION totp.urlencode; - -COMMIT; diff --git a/packages/totp/revert/schemas/totp/schema.sql b/packages/totp/revert/schemas/totp/schema.sql deleted file mode 100644 index 7a30640..0000000 --- a/packages/totp/revert/schemas/totp/schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert schemas/totp/schema from pg - -BEGIN; - -DROP SCHEMA totp CASCADE; - -COMMIT; diff --git a/packages/totp/sql/launchql-totp--0.4.6.sql b/packages/totp/sql/launchql-totp--0.4.6.sql deleted file mode 100644 index 3fa1120..0000000 --- a/packages/totp/sql/launchql-totp--0.4.6.sql +++ /dev/null @@ -1,173 +0,0 @@ -\echo Use "CREATE EXTENSION launchql-totp" to load this file. \quit -CREATE SCHEMA totp; - -CREATE FUNCTION totp.urlencode(in_str text) RETURNS text AS $EOFCODE$ -DECLARE - _i int4; - _temp varchar; - _ascii int4; - _result text := ''; -BEGIN - FOR _i IN 1..length(in_str) - LOOP - _temp := substr(in_str, _i, 1); - IF _temp ~ '[0-9a-zA-Z:/@._?#-]+' THEN - _result := _result || _temp; - ELSE - _ascii := ascii(_temp); - IF _ascii > x'07ff'::int4 THEN - RAISE exception 'won''t deal with 3 (or more) byte sequences.'; - END IF; - IF _ascii <= x'07f'::int4 THEN - _temp := '%' || to_hex(_ascii); - ELSE - _temp := '%' || to_hex((_ascii & x'03f'::int4) + x'80'::int4); - _ascii := _ascii >> 6; - _temp := '%' || to_hex((_ascii & x'01f'::int4) + x'c0'::int4) || _temp; - END IF; - _result := _result || upper(_temp); - END IF; - END LOOP; - RETURN _result; -END; -$EOFCODE$ LANGUAGE plpgsql STRICT IMMUTABLE; - -CREATE FUNCTION totp.pad_secret(input bytea, len int) RETURNS bytea AS $EOFCODE$ -DECLARE - output bytea; - orig_length int = octet_length(input); -BEGIN - IF (orig_length = len) THEN - RETURN input; - END IF; - - -- create blank bytea size of new length - output = lpad('', len, 'x')::bytea; - - FOR i IN 0 .. len-1 LOOP - output = set_byte(output, i, get_byte(input, i % orig_length)); - END LOOP; - - RETURN output; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION totp.base32_to_hex(input text) RETURNS text AS $EOFCODE$ -DECLARE - output text[]; - decoded text = base32.decode(input); - len int = character_length(decoded); - hx text; -BEGIN - - FOR i IN 1 .. len LOOP - hx = to_hex(ascii(substring(decoded from i for 1)))::text; - IF (character_length(hx) = 1) THEN - -- if it is odd number of digits, pad a 0 so it can later - hx = '0' || hx; - END IF; - output = array_append(output, hx); - END LOOP; - - RETURN array_to_string(output, ''); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION totp.hotp(key bytea, c int, digits int DEFAULT 6, hash text DEFAULT 'sha1') RETURNS text AS $EOFCODE$ -DECLARE - c BYTEA := '\x' || LPAD(TO_HEX(c), 16, '0'); - mac BYTEA := HMAC(c, key, hash); - trunc_offset INT := GET_BYTE(mac, length(mac) - 1) % 16; - result TEXT := SUBSTRING(SET_BIT(SUBSTRING(mac FROM 1 + trunc_offset FOR 4), 7, 0)::TEXT, 2)::BIT(32)::INT % (10 ^ digits)::INT; -BEGIN - RETURN LPAD(result, digits, '0'); -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION totp.generate(secret text, period int DEFAULT 30, digits int DEFAULT 6, time_from timestamptz DEFAULT now(), hash text DEFAULT 'sha1', encoding text DEFAULT 'base32', clock_offset int DEFAULT 0) RETURNS text AS $EOFCODE$ -DECLARE - c int := FLOOR(EXTRACT(EPOCH FROM time_from) / period)::int + clock_offset; - key bytea; -BEGIN - - IF (encoding = 'base32') THEN - key = ( '\x' || totp.base32_to_hex(secret) )::bytea; - ELSE - key = secret::bytea; - END IF; - - RETURN totp.hotp(key, c, digits, hash); -END; -$EOFCODE$ LANGUAGE plpgsql STABLE; - -CREATE FUNCTION totp.timing_safe_equals(a bytea, b bytea) RETURNS boolean AS $EOFCODE$ -DECLARE - la int := length(a); - lb int := length(b); - maxlen int := GREATEST(la, lb); - i int; - diff int := la # lb; - ca int; - cb int; -BEGIN - FOR i IN 0..(maxlen - 1) LOOP - ca := CASE WHEN i < la THEN get_byte(a, i) ELSE 0 END; - cb := CASE WHEN i < lb THEN get_byte(b, i) ELSE 0 END; - diff := diff | (ca # cb); - END LOOP; - RETURN diff = 0; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE STRICT; - -CREATE FUNCTION totp.timing_safe_equals(a text, b text) RETURNS boolean AS $EOFCODE$ --- Verify uses timing-safe equality to avoid leaking mismatch position via timing; do not use direct '=' here. --- See HN discussion for background: https://news.ycombinator.com/item?id=26260667 - - SELECT totp.timing_safe_equals(convert_to(a, 'UTF8'), convert_to(b, 'UTF8')); -$EOFCODE$ LANGUAGE sql IMMUTABLE STRICT; - -CREATE FUNCTION totp.verify(secret text, check_totp text, period int DEFAULT 30, digits int DEFAULT 6, time_from timestamptz DEFAULT now(), hash text DEFAULT 'sha1', encoding text DEFAULT 'base32', clock_offset int DEFAULT 0) RETURNS boolean AS $EOFCODE$ - SELECT totp.timing_safe_equals( - totp.generate( - secret, - period, - digits, - time_from, - hash, - encoding, - clock_offset - ), - check_totp - ); -$EOFCODE$ LANGUAGE sql; - -CREATE FUNCTION totp.url(email text, totp_secret text, totp_interval int, totp_issuer text) RETURNS text AS $EOFCODE$ - SELECT - concat('otpauth://totp/', totp.urlencode (email), '?secret=', totp.urlencode (totp_secret), '&period=', totp.urlencode (totp_interval::text), '&issuer=', totp.urlencode (totp_issuer)); -$EOFCODE$ LANGUAGE sql STRICT IMMUTABLE; - -CREATE FUNCTION totp.random_base32(_length int DEFAULT 20) RETURNS text LANGUAGE sql AS $EOFCODE$ - SELECT - string_agg( - ('{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,2,3,4,5,6,7}'::text[]) - [ (get_byte(b, i) % 32) + 1 ], - '' - ) - FROM (SELECT gen_random_bytes(_length) AS b) t, - LATERAL generate_series(0, _length - 1) g(i); -$EOFCODE$; - -CREATE FUNCTION totp.generate_secret(hash text DEFAULT 'sha1') RETURNS bytea AS $EOFCODE$ -BEGIN - -- See https://tools.ietf.org/html/rfc4868#section-2.1.2 - -- The optimal key length for HMAC is the block size of the algorithm - CASE - WHEN hash = 'sha1' THEN RETURN totp.random_base32(20); -- = 160 bits - WHEN hash = 'sha256' THEN RETURN totp.random_base32(32); -- = 256 bits - WHEN hash = 'sha512' THEN RETURN totp.random_base32(64); -- = 512 bits - ELSE - RAISE EXCEPTION 'Unsupported hash algorithm for OTP (see RFC6238/4226).'; - RETURN NULL; - END CASE; -END; -$EOFCODE$ LANGUAGE plpgsql VOLATILE; \ No newline at end of file diff --git a/packages/totp/verify/schemas/totp/procedures/generate_totp.sql b/packages/totp/verify/schemas/totp/procedures/generate_totp.sql deleted file mode 100644 index 7038d4f..0000000 --- a/packages/totp/verify/schemas/totp/procedures/generate_totp.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/totp/procedures/generate_totp on pg - -BEGIN; - -SELECT verify_function ('totp.generate'); - -ROLLBACK; diff --git a/packages/totp/verify/schemas/totp/procedures/random_base32.sql b/packages/totp/verify/schemas/totp/procedures/random_base32.sql deleted file mode 100644 index e518b9a..0000000 --- a/packages/totp/verify/schemas/totp/procedures/random_base32.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/totp/procedures/random_base32 on pg - -BEGIN; - -SELECT verify_function ('totp.random_base32'); - -ROLLBACK; diff --git a/packages/totp/verify/schemas/totp/procedures/urlencode.sql b/packages/totp/verify/schemas/totp/procedures/urlencode.sql deleted file mode 100644 index 78097d0..0000000 --- a/packages/totp/verify/schemas/totp/procedures/urlencode.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/totp/procedures/urlencode on pg - -BEGIN; - -SELECT verify_function ('totp.urlencode'); - -ROLLBACK; diff --git a/packages/totp/verify/schemas/totp/schema.sql b/packages/totp/verify/schemas/totp/schema.sql deleted file mode 100644 index a26e199..0000000 --- a/packages/totp/verify/schemas/totp/schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify schemas/totp/schema on pg - -BEGIN; - -SELECT verify_schema ('totp'); - -ROLLBACK; diff --git a/packages/verify/LICENSE b/packages/verify/LICENSE deleted file mode 100644 index 04b6583..0000000 --- a/packages/verify/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2025 Dan Lynch -Copyright (c) 2025 Interweb, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/verify/Makefile b/packages/verify/Makefile deleted file mode 100644 index abde382..0000000 --- a/packages/verify/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -EXTENSION = launchql-verify -DATA = sql/launchql-verify--0.4.6.sql - -PG_CONFIG = pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) diff --git a/packages/verify/README.md b/packages/verify/README.md deleted file mode 100644 index f8b97ef..0000000 --- a/packages/verify/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @lql-pg/verify - -Verification utilities for LaunchQL deploy/verify/revert workflow. - -Provides essential verification functions used by other modules to validate database changes, including verify_table, verify_index, verify_function, and other assertion utilities. diff --git a/packages/verify/__tests__/ext-verify.test.ts b/packages/verify/__tests__/ext-verify.test.ts deleted file mode 100644 index 3c21fa6..0000000 --- a/packages/verify/__tests__/ext-verify.test.ts +++ /dev/null @@ -1,490 +0,0 @@ -import { getConnections, PgTestClient } from 'pgsql-test'; -import { snapshot } from 'graphile-test'; - -let pg: PgTestClient; -let teardown: () => Promise; - -describe('ext-verify utilities', () => { - beforeAll(async () => { - ({ pg, teardown } = await getConnections()); - }); - - afterAll(async () => { - await teardown(); - }); - - beforeEach(async () => { - await pg.beforeEach(); - }); - - afterEach(async () => { - await pg.afterEach(); - }); - - describe('helper functions', () => { - it('get_entity_from_str should extract entity name', async () => { - const tests = [ - ['public.users', 'users'], - ['users', 'users'], - ['schema.table_name', 'table_name'], - ['my_function', 'my_function'] - ]; - - for (const [input, expected] of tests) { - const [result] = await pg.any( - `SELECT get_entity_from_str($1) as entity`, - [input] - ); - expect(result.entity).toBe(expected); - } - }); - - it('get_schema_from_str should extract schema name', async () => { - const tests = [ - ['public.users', 'public'], - ['users', 'public'], - ['my_schema.table_name', 'my_schema'], - ['function_name', 'public'] - ]; - - for (const [input, expected] of tests) { - const [result] = await pg.any( - `SELECT get_schema_from_str($1) as schema_name`, - [input] - ); - expect(result.schema_name).toBe(expected); - } - }); - }); - - describe('schema verification', () => { - it('verify_schema should return true for existing schema', async () => { - const [result] = await pg.any( - `SELECT verify_schema('public') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_schema should throw for non-existent schema', async () => { - await expect( - pg.any(`SELECT verify_schema('nonexistent_schema')`) - ).rejects.toThrow('Nonexistent schema'); - }); - }); - - describe('table verification', () => { - beforeEach(async () => { - // Create a test table - await pg.any(` - CREATE TABLE test_table ( - id serial PRIMARY KEY, - name text NOT NULL, - email text UNIQUE - ) - `); - }); - - it('verify_table should return true for existing table', async () => { - const [result] = await pg.any( - `SELECT verify_table('test_table') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_table should work with schema-qualified names', async () => { - const [result] = await pg.any( - `SELECT verify_table('public.test_table') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_table should throw for non-existent table', async () => { - await expect( - pg.any(`SELECT verify_table('nonexistent_table')`) - ).rejects.toThrow('Nonexistent table'); - }); - }); - - describe('constraint verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_constraints ( - id serial PRIMARY KEY, - name text NOT NULL, - email text UNIQUE, - age integer CHECK (age > 0) - ) - `); - }); - - it('verify_constraint should return true for existing constraint', async () => { - // Get the actual constraint name (PostgreSQL generates names) - const [constraint] = await pg.any(` - SELECT conname - FROM pg_constraint c - JOIN pg_class t ON c.conrelid = t.oid - WHERE t.relname = 'test_constraints' AND contype = 'u' - LIMIT 1 - `); - - if (constraint) { - const [result] = await pg.any( - `SELECT verify_constraint('test_constraints', $1) as verified`, - [constraint.conname] - ); - expect(result.verified).toBe(true); - } - }); - - it('verify_constraint should throw for non-existent constraint', async () => { - await expect( - pg.any(`SELECT verify_constraint('test_constraints', 'nonexistent_constraint')`) - ).rejects.toThrow('Nonexistent constraint'); - }); - }); - - describe('function verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE OR REPLACE FUNCTION test_function(x integer) - RETURNS integer AS $$ - BEGIN - RETURN x * 2; - END; - $$ LANGUAGE plpgsql; - `); - }); - - it('verify_function should return true for existing function', async () => { - const [result] = await pg.any( - `SELECT verify_function('test_function') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_function should work with schema-qualified names', async () => { - const [result] = await pg.any( - `SELECT verify_function('public.test_function') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_function should throw for non-existent function', async () => { - await expect( - pg.any(`SELECT verify_function('nonexistent_function')`) - ).rejects.toThrow('Nonexistent function'); - }); - }); - - describe('view verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_view_base ( - id serial PRIMARY KEY, - name text - ) - `); - - await pg.any(` - CREATE VIEW test_view AS - SELECT id, name FROM test_view_base WHERE name IS NOT NULL - `); - }); - - it('verify_view should return true for existing view', async () => { - const [result] = await pg.any( - `SELECT verify_view('test_view') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_view should work with schema-qualified names', async () => { - const [result] = await pg.any( - `SELECT verify_view('public.test_view') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_view should throw for non-existent view', async () => { - await expect( - pg.any(`SELECT verify_view('nonexistent_view')`) - ).rejects.toThrow('Nonexistent view'); - }); - }); - - describe('index verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_index_table ( - id serial PRIMARY KEY, - name text, - email text - ) - `); - - await pg.any(` - CREATE INDEX test_custom_index ON test_index_table (name) - `); - }); - - it('verify_index should return true for existing index', async () => { - const [result] = await pg.any( - `SELECT verify_index('test_index_table', 'test_custom_index') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_index should throw for non-existent index', async () => { - await expect( - pg.any(`SELECT verify_index('test_index_table', 'nonexistent_index')`) - ).rejects.toThrow('Nonexistent index'); - }); - - it('list_indexes should return index information', async () => { - const results = await pg.any( - `SELECT * FROM list_indexes('test_index_table', 'test_custom_index')` - ); - - expect(results).toHaveLength(1); - expect(results[0].schema_name).toBe('public'); - expect(results[0].table_name).toBe('test_index_table'); - expect(results[0].index_name).toBe('test_custom_index'); - }); - }); - - describe('trigger verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_trigger_table ( - id serial PRIMARY KEY, - name text, - updated_at timestamp DEFAULT now() - ) - `); - - await pg.any(` - CREATE OR REPLACE FUNCTION update_timestamp() - RETURNS trigger AS $$ - BEGIN - NEW.updated_at = now(); - RETURN NEW; - END; - $$ LANGUAGE plpgsql; - `); - - await pg.any(` - CREATE TRIGGER test_update_trigger - BEFORE UPDATE ON test_trigger_table - FOR EACH ROW EXECUTE FUNCTION update_timestamp() - `); - }); - - it('verify_trigger should return true for existing trigger', async () => { - const [result] = await pg.any( - `SELECT verify_trigger('test_update_trigger') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_trigger should throw for non-existent trigger', async () => { - await expect( - pg.any(`SELECT verify_trigger('nonexistent_trigger')`) - ).rejects.toThrow('Nonexistent trigger'); - }); - }); - - describe('type verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TYPE test_enum AS ENUM ('active', 'inactive', 'pending') - `); - - await pg.any(` - CREATE TYPE test_composite AS ( - name text, - value integer - ) - `); - }); - - it('verify_type should return true for existing enum type', async () => { - const [result] = await pg.any( - `SELECT verify_type('test_enum') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_type should return true for existing composite type', async () => { - const [result] = await pg.any( - `SELECT verify_type('test_composite') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_type should throw for non-existent type', async () => { - await expect( - pg.any(`SELECT verify_type('nonexistent_type')`) - ).rejects.toThrow('Nonexistent type'); - }); - }); - - describe('domain verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE DOMAIN test_domain AS text CHECK (length(value) > 0) - `); - }); - - it('verify_domain should return true for existing domain', async () => { - const [result] = await pg.any( - `SELECT verify_domain('test_domain') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_domain should throw for non-existent domain', async () => { - await expect( - pg.any(`SELECT verify_domain('nonexistent_domain')`) - ).rejects.toThrow('Nonexistent type'); - }); - }); - - describe('role and membership verification', () => { - it('verify_role should return true for current user', async () => { - // Get current user - const [currentUser] = await pg.any(`SELECT current_user as username`); - - const [result] = await pg.any( - `SELECT verify_role($1) as verified`, - [currentUser.username] - ); - expect(result.verified).toBe(true); - }); - - it('verify_role should throw for non-existent role', async () => { - await expect( - pg.any(`SELECT verify_role('nonexistent_user_12345')`) - ).rejects.toThrow('Nonexistent user'); - }); - - it('list_memberships should return role memberships', async () => { - const [currentUser] = await pg.any(`SELECT current_user as username`); - - const results = await pg.any( - `SELECT * FROM list_memberships($1)`, - [currentUser.username] - ); - - // Should at least include the user themselves - expect(results.length).toBeGreaterThan(0); - const usernames = results.map(r => r.rolname); - expect(usernames).toContain(currentUser.username); - }); - }); - - describe('grant verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_grants_table ( - id serial PRIMARY KEY, - data text - ) - `); - }); - - it('verify_table_grant should work for existing grants', async () => { - // Grant SELECT to current user (if not already granted) - const [currentUser] = await pg.any(`SELECT current_user as username`); - - // The table owner should have all privileges - const [result] = await pg.any( - `SELECT verify_table_grant('test_grants_table', 'INSERT', $1) as verified`, - [currentUser.username] - ); - expect(result.verified).toBe(true); - }); - }); - - describe('extension verification', () => { - it('verify_extension should return true for available extensions', async () => { - // Most PostgreSQL installations have the plpgsql extension available - const availableExtensions = await pg.any(` - SELECT name FROM pg_available_extensions - WHERE name IN ('plpgsql', 'uuid-ossp', 'pgcrypto') - LIMIT 1 - `); - - if (availableExtensions.length > 0) { - const [result] = await pg.any( - `SELECT verify_extension($1) as verified`, - [availableExtensions[0].name] - ); - expect(result.verified).toBe(true); - } else { - // Skip this test if no common extensions are available - expect(true).toBe(true); - } - }); - - it('verify_extension should throw for non-existent extension', async () => { - await expect( - pg.any(`SELECT verify_extension('definitely_nonexistent_extension_12345')`) - ).rejects.toThrow('Nonexistent extension'); - }); - }); - - describe('security verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_security_table ( - id serial PRIMARY KEY, - user_id text, - data text - ) - `); - - // Enable row level security - await pg.any(`ALTER TABLE test_security_table ENABLE ROW LEVEL SECURITY`); - }); - - it('verify_security should return true for tables with RLS enabled', async () => { - const [result] = await pg.any( - `SELECT verify_security('test_security_table') as verified` - ); - expect(result.verified).toBe(true); - }); - }); - - describe('policy verification', () => { - beforeEach(async () => { - await pg.any(` - CREATE TABLE test_policy_table ( - id serial PRIMARY KEY, - user_id text, - data text - ) - `); - - await pg.any(`ALTER TABLE test_policy_table ENABLE ROW LEVEL SECURITY`); - - await pg.any(` - CREATE POLICY test_policy ON test_policy_table - FOR SELECT - USING (user_id = current_user) - `); - }); - - it('verify_policy should return true for existing policy', async () => { - const [result] = await pg.any( - `SELECT verify_policy('test_policy', 'test_policy_table') as verified` - ); - expect(result.verified).toBe(true); - }); - - it('verify_policy should throw for non-existent policy', async () => { - await expect( - pg.any(`SELECT verify_policy('nonexistent_policy', 'test_policy_table')`) - ).rejects.toThrow('Nonexistent policy'); - }); - }); -}); \ No newline at end of file diff --git a/packages/verify/deploy/procedures/get_entity_from_str.sql b/packages/verify/deploy/procedures/get_entity_from_str.sql deleted file mode 100644 index 1c29a05..0000000 --- a/packages/verify/deploy/procedures/get_entity_from_str.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Deploy procedures/get_entity_from_str to pg - -BEGIN; -CREATE FUNCTION get_entity_from_str (qualified_name text) - RETURNS text - AS $$ -DECLARE - parts text[]; -BEGIN - SELECT - parse_ident(qualified_name) INTO parts; - IF cardinality(parts) > 1 THEN - RETURN parts[2]; - ELSE - RETURN parts[1]; - END IF; -END; -$$ -LANGUAGE plpgsql -STRICT; -COMMIT; diff --git a/packages/verify/deploy/procedures/get_schema_from_str.sql b/packages/verify/deploy/procedures/get_schema_from_str.sql deleted file mode 100644 index 5a833a1..0000000 --- a/packages/verify/deploy/procedures/get_schema_from_str.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Deploy procedures/get_schema_from_str to pg -BEGIN; -CREATE FUNCTION get_schema_from_str (qualified_name text) - RETURNS text - AS $$ -DECLARE - parts text[]; -BEGIN - SELECT - parse_ident(qualified_name) INTO parts; - IF cardinality(parts) > 1 THEN - RETURN parts[1]; - ELSE - RETURN 'public'; - END IF; -END; -$$ -LANGUAGE plpgsql -STRICT; -COMMIT; - diff --git a/packages/verify/deploy/procedures/list_indexes.sql b/packages/verify/deploy/procedures/list_indexes.sql deleted file mode 100644 index 9e36f6f..0000000 --- a/packages/verify/deploy/procedures/list_indexes.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Deploy procedures/list_indexes to pg --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - -BEGIN; - -CREATE FUNCTION list_indexes (_table text, _index text) - RETURNS TABLE (schema_name text, table_name text, index_name text) -AS $$ -SELECT - n.nspname::text AS schema_name, - t.relname::text AS table_name, - i.relname::text AS index_name -FROM - pg_class t, - pg_class i, - pg_index ix, - pg_catalog.pg_namespace n -WHERE - t.oid = ix.indrelid - AND i.oid = ix.indexrelid - AND n.oid = i.relnamespace - AND n.nspname = get_schema_from_str(_table) - AND i.relname = _index - AND t.relname = get_entity_from_str(_table); -$$ -LANGUAGE 'sql' IMMUTABLE; - -COMMIT; diff --git a/packages/verify/deploy/procedures/list_memberships.sql b/packages/verify/deploy/procedures/list_memberships.sql deleted file mode 100644 index ad3d566..0000000 --- a/packages/verify/deploy/procedures/list_memberships.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Deploy procedures/list_memberships to pg - -BEGIN; - -CREATE FUNCTION list_memberships (_user text) - RETURNS TABLE (rolname text) -AS $$ WITH RECURSIVE cte AS ( - SELECT - oid - FROM - pg_roles - WHERE - rolname = _user - UNION ALL - SELECT - m.roleid - FROM - cte - JOIN pg_auth_members m ON m.member = cte.oid -) -SELECT - pg_roles.rolname::text AS rolname -FROM - cte c, - pg_roles -WHERE - pg_roles.oid = c.oid; -$$ -LANGUAGE 'sql' IMMUTABLE; - -COMMIT; diff --git a/packages/verify/deploy/procedures/verify_constraint.sql b/packages/verify/deploy/procedures/verify_constraint.sql deleted file mode 100644 index 048231d..0000000 --- a/packages/verify/deploy/procedures/verify_constraint.sql +++ /dev/null @@ -1,27 +0,0 @@ --- Deploy procedures/verify_constraint to pg -BEGIN; --- https://stackoverflow.com/questions/20087259/how-to-find-whether-unique-key-constraint-exists-for-given-columns -CREATE FUNCTION verify_constraint (_table text, _name text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - c.conname, - pg_get_constraintdef(c.oid) - FROM - pg_constraint c - WHERE - conname = _name - AND c.conrelid = _table::regclass) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent constraint --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_domain.sql b/packages/verify/deploy/procedures/verify_domain.sql deleted file mode 100644 index df3c4c3..0000000 --- a/packages/verify/deploy/procedures/verify_domain.sql +++ /dev/null @@ -1,32 +0,0 @@ --- Deploy procedures/verify_domain to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - -BEGIN; -CREATE FUNCTION verify_domain (_type text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - pg_type.typname, - n.nspname - FROM - pg_type, - pg_catalog.pg_namespace n - WHERE - typtype = 'd' - AND typname = get_entity_from_str (_type) - AND nspname = get_schema_from_str (_type)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent type --> %', _type - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_extension.sql b/packages/verify/deploy/procedures/verify_extension.sql deleted file mode 100644 index 08caacf..0000000 --- a/packages/verify/deploy/procedures/verify_extension.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Deploy procedures/verify_extension to pg -BEGIN; -CREATE FUNCTION verify_extension (_extname text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_available_extensions - WHERE - name = _extname) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent extension --> %', _extname - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_function.sql b/packages/verify/deploy/procedures/verify_function.sql deleted file mode 100644 index 823e1e2..0000000 --- a/packages/verify/deploy/procedures/verify_function.sql +++ /dev/null @@ -1,37 +0,0 @@ --- Deploy procedures/verify_function to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - -BEGIN; -CREATE FUNCTION verify_function (_name text, _user text DEFAULT NULL) - RETURNS boolean - AS $$ -DECLARE - check_user text; -BEGIN - IF (_user IS NOT NULL) THEN - check_user = _user; - ELSE - check_user = CURRENT_USER; - END IF; - IF EXISTS ( - SELECT - has_function_privilege(check_user, p.oid, 'execute') - FROM - pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - WHERE - n.nspname = get_schema_from_str (_name) - AND p.proname = get_entity_from_str (_name)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent function --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_index.sql b/packages/verify/deploy/procedures/verify_index.sql deleted file mode 100644 index f073ea9..0000000 --- a/packages/verify/deploy/procedures/verify_index.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Deploy procedures/verify_index to pg - --- requires: procedures/list_indexes - -BEGIN; -CREATE FUNCTION verify_index (_table text, _index text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - list_indexes (_table, _index)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent index --> %', _index - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_membership.sql b/packages/verify/deploy/procedures/verify_membership.sql deleted file mode 100644 index b8edc2f..0000000 --- a/packages/verify/deploy/procedures/verify_membership.sql +++ /dev/null @@ -1,27 +0,0 @@ --- Deploy procedures/verify_membership to pg - --- requires: procedures/list_memberships - -BEGIN; -CREATE FUNCTION verify_membership (_user text, _role text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - list_memberships (_user) - WHERE - rolname = _role) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent member --> %', _user - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_policy.sql b/packages/verify/deploy/procedures/verify_policy.sql deleted file mode 100644 index 4b37f53..0000000 --- a/packages/verify/deploy/procedures/verify_policy.sql +++ /dev/null @@ -1,35 +0,0 @@ --- Deploy procedures/verify_policy to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - - -BEGIN; - -CREATE FUNCTION verify_policy (_policy text, _table text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_class p - JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace - JOIN pg_policy pol ON pol.polrelid = p.relfilenode - WHERE - pol.polname = _policy - AND relrowsecurity = 'true' - AND relname = get_entity_from_str (_table) - AND nspname = get_schema_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent policy or missing relrowsecurity --> %', _policy - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_role.sql b/packages/verify/deploy/procedures/verify_role.sql deleted file mode 100644 index 5c87261..0000000 --- a/packages/verify/deploy/procedures/verify_role.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Deploy procedures/verify_role to pg -BEGIN; -CREATE FUNCTION verify_role (_user text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_roles - WHERE - rolname = _user) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent user --> %', _user - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_schema.sql b/packages/verify/deploy/procedures/verify_schema.sql deleted file mode 100644 index 3eba268..0000000 --- a/packages/verify/deploy/procedures/verify_schema.sql +++ /dev/null @@ -1,24 +0,0 @@ --- Deploy procedures/verify_schema to pg -BEGIN; -CREATE FUNCTION verify_schema (_schema text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - pg_catalog.pg_namespace - WHERE - nspname = _schema) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent schema --> %', _schema - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_security.sql b/packages/verify/deploy/procedures/verify_security.sql deleted file mode 100644 index 21c7aad..0000000 --- a/packages/verify/deploy/procedures/verify_security.sql +++ /dev/null @@ -1,33 +0,0 @@ --- Deploy procedures/verify_security to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - -BEGIN; -CREATE FUNCTION verify_security (_table text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - n.oid, - relname, - n.nspname - FROM - pg_class p - JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace - WHERE - relrowsecurity = 'true' - AND relname = get_entity_from_str (_table) - AND nspname = get_schema_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent security --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_table.sql b/packages/verify/deploy/procedures/verify_table.sql deleted file mode 100644 index b43ee67..0000000 --- a/packages/verify/deploy/procedures/verify_table.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Deploy procedures/verify_table to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - - -BEGIN; -CREATE FUNCTION verify_table (_table text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - information_schema.tables - WHERE - table_schema = get_schema_from_str (_table) - AND table_name = get_entity_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent table --> %', _table - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_table_grant.sql b/packages/verify/deploy/procedures/verify_table_grant.sql deleted file mode 100644 index 26a239d..0000000 --- a/packages/verify/deploy/procedures/verify_table_grant.sql +++ /dev/null @@ -1,32 +0,0 @@ --- Deploy procedures/verify_table_grant to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - -BEGIN; -CREATE FUNCTION verify_table_grant (_table text, _privilege text, _role text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - grantee, - privilege_type - FROM - information_schema.role_table_grants - WHERE - table_schema = get_schema_from_str (_table) - AND table_name = get_entity_from_str (_table) - AND privilege_type = _privilege - AND grantee = _role) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent table grant --> %', _privilege - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_trigger.sql b/packages/verify/deploy/procedures/verify_trigger.sql deleted file mode 100644 index e74fa83..0000000 --- a/packages/verify/deploy/procedures/verify_trigger.sql +++ /dev/null @@ -1,32 +0,0 @@ --- Deploy procedures/verify_trigger to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - - -BEGIN; -CREATE FUNCTION verify_trigger (_trigger text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - pg_trigger.tgname, - n.nspname - FROM - pg_trigger, - pg_catalog.pg_namespace n - WHERE - tgname = get_entity_from_str (_trigger) - AND nspname = get_schema_from_str (_trigger)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent trigger --> %', _trigger - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_type.sql b/packages/verify/deploy/procedures/verify_type.sql deleted file mode 100644 index 7dd1c1d..0000000 --- a/packages/verify/deploy/procedures/verify_type.sql +++ /dev/null @@ -1,32 +0,0 @@ --- Deploy procedures/verify_type to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - - -BEGIN; -CREATE FUNCTION verify_type (_type text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - pg_type.typname, - n.nspname - FROM - pg_type, - pg_catalog.pg_namespace n - WHERE - typname = get_entity_from_str (_type) - AND nspname = get_schema_from_str (_type)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent type --> %', _type - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/deploy/procedures/verify_view.sql b/packages/verify/deploy/procedures/verify_view.sql deleted file mode 100644 index 09fff01..0000000 --- a/packages/verify/deploy/procedures/verify_view.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Deploy procedures/verify_view to pg - --- requires: procedures/get_entity_from_str --- requires: procedures/get_schema_from_str - - -BEGIN; -CREATE FUNCTION verify_view (_view text) - RETURNS boolean - AS $$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - information_schema.views - WHERE - table_schema = get_schema_from_str (_view) - AND table_name = get_entity_from_str (_view)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent view --> %', _view - USING HINT = 'Please check'; -END IF; -END; -$$ -LANGUAGE 'plpgsql' -IMMUTABLE; -COMMIT; - diff --git a/packages/verify/jest.config.js b/packages/verify/jest.config.js deleted file mode 100644 index e20e7ef..0000000 --- a/packages/verify/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - - // Match both __tests__ and colocated test files - testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'], - - // Ignore build artifacts and type declarations - testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'], - modulePathIgnorePatterns: ['/dist/'], - watchPathIgnorePatterns: ['/dist/'], - - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], -}; diff --git a/packages/verify/launchql-verify.control b/packages/verify/launchql-verify.control deleted file mode 100644 index 89630b3..0000000 --- a/packages/verify/launchql-verify.control +++ /dev/null @@ -1,8 +0,0 @@ -# launchql-verify extension -comment = 'launchql-verify extension' -default_version = '0.4.6' -module_pathname = '$libdir/launchql-verify' -requires = 'plpgsql' -relocatable = false -superuser = false - \ No newline at end of file diff --git a/packages/verify/launchql.plan b/packages/verify/launchql.plan deleted file mode 100644 index 98c9682..0000000 --- a/packages/verify/launchql.plan +++ /dev/null @@ -1,24 +0,0 @@ -%syntax-version=1.0.0 -%project=launchql-verify -%uri=launchql-verify - -procedures/get_entity_from_str 2017-08-11T08:11:51Z skitch # add procedures/get_entity_from_str -procedures/get_schema_from_str 2017-08-11T08:11:51Z skitch # add procedures/get_schema_from_str -procedures/list_indexes [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/list_indexes -procedures/list_memberships 2017-08-11T08:11:51Z skitch # add procedures/list_memberships -procedures/verify_constraint 2017-08-11T08:11:51Z skitch # add procedures/verify_constraint -procedures/verify_domain [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_domain -procedures/verify_extension 2017-08-11T08:11:51Z skitch # add procedures/verify_extension -procedures/verify_function [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_function -procedures/verify_index [procedures/list_indexes] 2017-08-11T08:11:51Z skitch # add procedures/verify_index -procedures/verify_membership [procedures/list_memberships] 2017-08-11T08:11:51Z skitch # add procedures/verify_membership -procedures/verify_policy [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_policy -procedures/verify_role 2017-08-11T08:11:51Z skitch # add procedures/verify_role -procedures/verify_schema 2017-08-11T08:11:51Z skitch # add procedures/verify_schema -procedures/verify_security [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_security -procedures/verify_table_grant [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_table_grant -procedures/verify_table [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_table -procedures/verify_trigger [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_trigger -procedures/verify_type [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_type -procedures/verify_view [procedures/get_entity_from_str procedures/get_schema_from_str] 2017-08-11T08:11:51Z skitch # add procedures/verify_view -@0.1.0 2025-08-26T23:56:48Z launchql # verify diff --git a/packages/verify/package.json b/packages/verify/package.json deleted file mode 100644 index 8468a81..0000000 --- a/packages/verify/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@lql-pg/verify", - "version": "0.4.6", - "description": "Verification utilities for LaunchQL deploy/verify/revert workflow", - "publishConfig": { - "access": "public" - }, - "scripts": { - "bundle": "lql package", - "test": "jest", - "test:watch": "jest --watch" - }, - "devDependencies": { - "@launchql/cli": "^4.9.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/launchql/extensions" - }, - "homepage": "https://github.com/launchql/extensions", - "bugs": { - "url": "https://github.com/launchql/extensions/issues" - } -} diff --git a/packages/verify/revert/procedures/get_entity_from_str.sql b/packages/verify/revert/procedures/get_entity_from_str.sql deleted file mode 100644 index 29e8c89..0000000 --- a/packages/verify/revert/procedures/get_entity_from_str.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert launchql-verify:procedures/get_entity_from_str from pg - -BEGIN; - -DROP FUNCTION get_entity_from_str(text); - -COMMIT; diff --git a/packages/verify/revert/procedures/get_schema_from_str.sql b/packages/verify/revert/procedures/get_schema_from_str.sql deleted file mode 100644 index 34e58e5..0000000 --- a/packages/verify/revert/procedures/get_schema_from_str.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert launchql-verify:procedures/get_schema_from_str from pg - -BEGIN; - -DROP FUNCTION get_schema_from_str(text); - -COMMIT; diff --git a/packages/verify/revert/procedures/list_indexes.sql b/packages/verify/revert/procedures/list_indexes.sql deleted file mode 100644 index 8b5b86d..0000000 --- a/packages/verify/revert/procedures/list_indexes.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert launchql-verify:procedures/list_indexes from pg - -BEGIN; - -DROP FUNCTION list_indexes(text, text); - -COMMIT; diff --git a/packages/verify/revert/procedures/list_memberships.sql b/packages/verify/revert/procedures/list_memberships.sql deleted file mode 100644 index 385f170..0000000 --- a/packages/verify/revert/procedures/list_memberships.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert launchql-verify:procedures/list_memberships from pg - -BEGIN; - -DROP FUNCTION list_memberships(text); - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_constraint.sql b/packages/verify/revert/procedures/verify_constraint.sql deleted file mode 100644 index 0db5938..0000000 --- a/packages/verify/revert/procedures/verify_constraint.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_constraint from pg - -BEGIN; - -DROP FUNCTION verify_constraint; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_domain.sql b/packages/verify/revert/procedures/verify_domain.sql deleted file mode 100644 index d7a2563..0000000 --- a/packages/verify/revert/procedures/verify_domain.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_domain from pg - -BEGIN; - -DROP FUNCTION public.verify_domain; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_extension.sql b/packages/verify/revert/procedures/verify_extension.sql deleted file mode 100644 index 95fb3df..0000000 --- a/packages/verify/revert/procedures/verify_extension.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_extension from pg - -BEGIN; - -DROP FUNCTION public.verify_extension; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_function.sql b/packages/verify/revert/procedures/verify_function.sql deleted file mode 100644 index 457a7da..0000000 --- a/packages/verify/revert/procedures/verify_function.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_function from pg - -BEGIN; - -DROP FUNCTION verify_function; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_index.sql b/packages/verify/revert/procedures/verify_index.sql deleted file mode 100644 index 6ddda34..0000000 --- a/packages/verify/revert/procedures/verify_index.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_index from pg - -BEGIN; - -DROP FUNCTION verify_index; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_membership.sql b/packages/verify/revert/procedures/verify_membership.sql deleted file mode 100644 index 62e69dd..0000000 --- a/packages/verify/revert/procedures/verify_membership.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_membership from pg - -BEGIN; - -DROP FUNCTION verify_membership; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_policy.sql b/packages/verify/revert/procedures/verify_policy.sql deleted file mode 100644 index e16b7b5..0000000 --- a/packages/verify/revert/procedures/verify_policy.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_policy from pg - -BEGIN; - -DROP FUNCTION verify_policy; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_role.sql b/packages/verify/revert/procedures/verify_role.sql deleted file mode 100644 index 5c649e8..0000000 --- a/packages/verify/revert/procedures/verify_role.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_role from pg - -BEGIN; - -DROP FUNCTION verify_role; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_schema.sql b/packages/verify/revert/procedures/verify_schema.sql deleted file mode 100644 index c058f28..0000000 --- a/packages/verify/revert/procedures/verify_schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_schema from pg - -BEGIN; - -DROP FUNCTION verify_schema; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_security.sql b/packages/verify/revert/procedures/verify_security.sql deleted file mode 100644 index 3b58b2f..0000000 --- a/packages/verify/revert/procedures/verify_security.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_security from pg - -BEGIN; - -DROP FUNCTION verify_security; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_table.sql b/packages/verify/revert/procedures/verify_table.sql deleted file mode 100644 index d2235bd..0000000 --- a/packages/verify/revert/procedures/verify_table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_table from pg - -BEGIN; - -DROP FUNCTION verify_table; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_table_grant.sql b/packages/verify/revert/procedures/verify_table_grant.sql deleted file mode 100644 index 780e849..0000000 --- a/packages/verify/revert/procedures/verify_table_grant.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_table_grant from pg - -BEGIN; - -DROP FUNCTION verify_table_grant; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_trigger.sql b/packages/verify/revert/procedures/verify_trigger.sql deleted file mode 100644 index c51d8a2..0000000 --- a/packages/verify/revert/procedures/verify_trigger.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_trigger from pg - -BEGIN; - -DROP FUNCTION verify_trigger; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_type.sql b/packages/verify/revert/procedures/verify_type.sql deleted file mode 100644 index da4e1e6..0000000 --- a/packages/verify/revert/procedures/verify_type.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_type from pg - -BEGIN; - -DROP FUNCTION verify_type; - -COMMIT; diff --git a/packages/verify/revert/procedures/verify_view.sql b/packages/verify/revert/procedures/verify_view.sql deleted file mode 100644 index 632e34a..0000000 --- a/packages/verify/revert/procedures/verify_view.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert procedures/verify_view from pg - -BEGIN; - -DROP FUNCTION public.verify_view; - -COMMIT; diff --git a/packages/verify/sql/launchql-verify--0.4.6.sql b/packages/verify/sql/launchql-verify--0.4.6.sql deleted file mode 100644 index c20ea9f..0000000 --- a/packages/verify/sql/launchql-verify--0.4.6.sql +++ /dev/null @@ -1,358 +0,0 @@ -\echo Use "CREATE EXTENSION launchql-verify" to load this file. \quit -CREATE FUNCTION get_entity_from_str(qualified_name text) RETURNS text AS $EOFCODE$ -DECLARE - parts text[]; -BEGIN - SELECT - parse_ident(qualified_name) INTO parts; - IF cardinality(parts) > 1 THEN - RETURN parts[2]; - ELSE - RETURN parts[1]; - END IF; -END; -$EOFCODE$ LANGUAGE plpgsql STRICT; - -CREATE FUNCTION get_schema_from_str(qualified_name text) RETURNS text AS $EOFCODE$ -DECLARE - parts text[]; -BEGIN - SELECT - parse_ident(qualified_name) INTO parts; - IF cardinality(parts) > 1 THEN - RETURN parts[1]; - ELSE - RETURN 'public'; - END IF; -END; -$EOFCODE$ LANGUAGE plpgsql STRICT; - -CREATE FUNCTION list_indexes(_table text, _index text) RETURNS TABLE ( schema_name text, table_name text, index_name text ) AS $EOFCODE$ -SELECT - n.nspname::text AS schema_name, - t.relname::text AS table_name, - i.relname::text AS index_name -FROM - pg_class t, - pg_class i, - pg_index ix, - pg_catalog.pg_namespace n -WHERE - t.oid = ix.indrelid - AND i.oid = ix.indexrelid - AND n.oid = i.relnamespace - AND n.nspname = get_schema_from_str(_table) - AND i.relname = _index - AND t.relname = get_entity_from_str(_table); -$EOFCODE$ LANGUAGE sql IMMUTABLE; - -CREATE FUNCTION list_memberships(_user text) RETURNS TABLE ( rolname text ) AS $EOFCODE$ WITH RECURSIVE cte AS ( - SELECT - oid - FROM - pg_roles - WHERE - rolname = _user - UNION ALL - SELECT - m.roleid - FROM - cte - JOIN pg_auth_members m ON m.member = cte.oid -) -SELECT - pg_roles.rolname::text AS rolname -FROM - cte c, - pg_roles -WHERE - pg_roles.oid = c.oid; -$EOFCODE$ LANGUAGE sql IMMUTABLE; - -CREATE FUNCTION verify_constraint(_table text, _name text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - c.conname, - pg_get_constraintdef(c.oid) - FROM - pg_constraint c - WHERE - conname = _name - AND c.conrelid = _table::regclass) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent constraint --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_domain(_type text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - pg_type.typname, - n.nspname - FROM - pg_type, - pg_catalog.pg_namespace n - WHERE - typtype = 'd' - AND typname = get_entity_from_str (_type) - AND nspname = get_schema_from_str (_type)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent type --> %', _type - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_extension(_extname text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_available_extensions - WHERE - name = _extname) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent extension --> %', _extname - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_function(_name text, _user text DEFAULT NULL) RETURNS boolean AS $EOFCODE$ -DECLARE - check_user text; -BEGIN - IF (_user IS NOT NULL) THEN - check_user = _user; - ELSE - check_user = CURRENT_USER; - END IF; - IF EXISTS ( - SELECT - has_function_privilege(check_user, p.oid, 'execute') - FROM - pg_catalog.pg_proc p - JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - WHERE - n.nspname = get_schema_from_str (_name) - AND p.proname = get_entity_from_str (_name)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent function --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_index(_table text, _index text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - list_indexes (_table, _index)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent index --> %', _index - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_membership(_user text, _role text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - list_memberships (_user) - WHERE - rolname = _role) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent member --> %', _user - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_policy(_policy text, _table text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_class p - JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace - JOIN pg_policy pol ON pol.polrelid = p.relfilenode - WHERE - pol.polname = _policy - AND relrowsecurity = 'true' - AND relname = get_entity_from_str (_table) - AND nspname = get_schema_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent policy or missing relrowsecurity --> %', _policy - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_role(_user text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - 1 - FROM - pg_roles - WHERE - rolname = _user) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent user --> %', _user - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_schema(_schema text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - pg_catalog.pg_namespace - WHERE - nspname = _schema) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent schema --> %', _schema - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_security(_table text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - n.oid, - relname, - n.nspname - FROM - pg_class p - JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace - WHERE - relrowsecurity = 'true' - AND relname = get_entity_from_str (_table) - AND nspname = get_schema_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent security --> %', _name - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_table_grant(_table text, _privilege text, _role text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - grantee, - privilege_type - FROM - information_schema.role_table_grants - WHERE - table_schema = get_schema_from_str (_table) - AND table_name = get_entity_from_str (_table) - AND privilege_type = _privilege - AND grantee = _role) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent table grant --> %', _privilege - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_table(_table text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - information_schema.tables - WHERE - table_schema = get_schema_from_str (_table) - AND table_name = get_entity_from_str (_table)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent table --> %', _table - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_trigger(_trigger text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - pg_trigger.tgname, - n.nspname - FROM - pg_trigger, - pg_catalog.pg_namespace n - WHERE - tgname = get_entity_from_str (_trigger) - AND nspname = get_schema_from_str (_trigger)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent trigger --> %', _trigger - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_type(_type text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - pg_type.typname, - n.nspname - FROM - pg_type, - pg_catalog.pg_namespace n - WHERE - typname = get_entity_from_str (_type) - AND nspname = get_schema_from_str (_type)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent type --> %', _type - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; - -CREATE FUNCTION verify_view(_view text) RETURNS boolean AS $EOFCODE$ -BEGIN - IF EXISTS ( - SELECT - * - FROM - information_schema.views - WHERE - table_schema = get_schema_from_str (_view) - AND table_name = get_entity_from_str (_view)) THEN - RETURN TRUE; -ELSE - RAISE EXCEPTION 'Nonexistent view --> %', _view - USING HINT = 'Please check'; -END IF; -END; -$EOFCODE$ LANGUAGE plpgsql IMMUTABLE; \ No newline at end of file diff --git a/packages/verify/verify/procedures/get_entity_from_str.sql b/packages/verify/verify/procedures/get_entity_from_str.sql deleted file mode 100644 index b9e1a74..0000000 --- a/packages/verify/verify/procedures/get_entity_from_str.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify launchql-verify:procedures/get_entity_from_str on pg - -BEGIN; - --- XXX Add verifications here. - -ROLLBACK; diff --git a/packages/verify/verify/procedures/get_schema_from_str.sql b/packages/verify/verify/procedures/get_schema_from_str.sql deleted file mode 100644 index 732d703..0000000 --- a/packages/verify/verify/procedures/get_schema_from_str.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify launchql-verify:procedures/get_schema_from_str on pg - -BEGIN; - --- XXX Add verifications here. - -ROLLBACK; diff --git a/packages/verify/verify/procedures/list_indexes.sql b/packages/verify/verify/procedures/list_indexes.sql deleted file mode 100644 index c6c4e43..0000000 --- a/packages/verify/verify/procedures/list_indexes.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify launchql-verify:procedures/list_indexes on pg - -BEGIN; - --- XXX Add verifications here. - -ROLLBACK; diff --git a/packages/verify/verify/procedures/list_memberships.sql b/packages/verify/verify/procedures/list_memberships.sql deleted file mode 100644 index 13fe36c..0000000 --- a/packages/verify/verify/procedures/list_memberships.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify launchql-verify:procedures/list_memberships on pg - -BEGIN; - --- XXX Add verifications here. - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_constraint.sql b/packages/verify/verify/procedures/verify_constraint.sql deleted file mode 100644 index fc98e32..0000000 --- a/packages/verify/verify/procedures/verify_constraint.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_constraint on pg - -BEGIN; - -SELECT verify_function ('public.verify_constraint'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_domain.sql b/packages/verify/verify/procedures/verify_domain.sql deleted file mode 100644 index 44eba89..0000000 --- a/packages/verify/verify/procedures/verify_domain.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_domain on pg - -BEGIN; - -SELECT verify_function ('public.verify_domain'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_extension.sql b/packages/verify/verify/procedures/verify_extension.sql deleted file mode 100644 index 5d1d2b8..0000000 --- a/packages/verify/verify/procedures/verify_extension.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_extension on pg - -BEGIN; - -SELECT verify_function ('public.verify_extension'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_function.sql b/packages/verify/verify/procedures/verify_function.sql deleted file mode 100644 index 857015e..0000000 --- a/packages/verify/verify/procedures/verify_function.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_function on pg - -BEGIN; - -SELECT verify_function ('public.verify_function'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_index.sql b/packages/verify/verify/procedures/verify_index.sql deleted file mode 100644 index ce75113..0000000 --- a/packages/verify/verify/procedures/verify_index.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_index on pg - -BEGIN; - -SELECT verify_function ('public.verify_index'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_membership.sql b/packages/verify/verify/procedures/verify_membership.sql deleted file mode 100644 index 547dfc4..0000000 --- a/packages/verify/verify/procedures/verify_membership.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_membership on pg - -BEGIN; - -SELECT verify_function ('public.verify_membership'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_policy.sql b/packages/verify/verify/procedures/verify_policy.sql deleted file mode 100644 index 36dd664..0000000 --- a/packages/verify/verify/procedures/verify_policy.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_policy on pg - -BEGIN; - -SELECT verify_function ('public.verify_policy'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_role.sql b/packages/verify/verify/procedures/verify_role.sql deleted file mode 100644 index 717b117..0000000 --- a/packages/verify/verify/procedures/verify_role.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_role on pg - -BEGIN; - -SELECT verify_function ('public.verify_role'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_schema.sql b/packages/verify/verify/procedures/verify_schema.sql deleted file mode 100644 index 6420dcb..0000000 --- a/packages/verify/verify/procedures/verify_schema.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_schema on pg - -BEGIN; - -SELECT verify_function ('public.verify_schema'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_security.sql b/packages/verify/verify/procedures/verify_security.sql deleted file mode 100644 index a4e570a..0000000 --- a/packages/verify/verify/procedures/verify_security.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_security on pg - -BEGIN; - -SELECT verify_function ('public.verify_security'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_table.sql b/packages/verify/verify/procedures/verify_table.sql deleted file mode 100644 index 5a198e3..0000000 --- a/packages/verify/verify/procedures/verify_table.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_table on pg - -BEGIN; - -SELECT verify_function ('public.verify_table'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_table_grant.sql b/packages/verify/verify/procedures/verify_table_grant.sql deleted file mode 100644 index 10efe5a..0000000 --- a/packages/verify/verify/procedures/verify_table_grant.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_table_grant on pg - -BEGIN; - -SELECT verify_function ('public.verify_table_grant'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_trigger.sql b/packages/verify/verify/procedures/verify_trigger.sql deleted file mode 100644 index 6234bb8..0000000 --- a/packages/verify/verify/procedures/verify_trigger.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_trigger on pg - -BEGIN; - -SELECT verify_function ('public.verify_trigger'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_type.sql b/packages/verify/verify/procedures/verify_type.sql deleted file mode 100644 index 58eb778..0000000 --- a/packages/verify/verify/procedures/verify_type.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_type on pg - -BEGIN; - -SELECT verify_function ('public.verify_type'); - -ROLLBACK; diff --git a/packages/verify/verify/procedures/verify_view.sql b/packages/verify/verify/procedures/verify_view.sql deleted file mode 100644 index 3d4e443..0000000 --- a/packages/verify/verify/procedures/verify_view.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify procedures/verify_view on pg - -BEGIN; - -SELECT verify_function ('public.verify_view'); - -ROLLBACK; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a08dda2..193f194 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,40 +72,7 @@ importers: specifier: ^5.1.6 version: 5.9.2 - packages/base32: - dependencies: - '@lql-pg/verify': - specifier: workspace:* - version: link:../verify - devDependencies: - '@launchql/cli': - specifier: ^4.9.0 - version: 4.9.0(@babel/core@7.28.0)(@pgsql/types@17.6.1)(graphile-build-pg@4.14.1(graphql@15.5.2)(pg@8.16.3))(graphql@15.5.2)(pg-sql2@4.14.1(pg@8.16.3))(postgraphile-core@4.14.1(graphql@15.5.2)(pg@8.16.3)) - packages/rls-demo: - dependencies: - '@lql-pg/verify': - specifier: workspace:* - version: link:../verify - devDependencies: - '@launchql/cli': - specifier: ^4.9.0 - version: 4.9.0(@babel/core@7.28.0)(@pgsql/types@17.6.1)(graphile-build-pg@4.14.1(graphql@15.5.2)(pg@8.16.3))(graphql@15.5.2)(pg-sql2@4.14.1(pg@8.16.3))(postgraphile-core@4.14.1(graphql@15.5.2)(pg@8.16.3)) - - packages/totp: - dependencies: - '@lql-pg/base32': - specifier: workspace:* - version: link:../base32 - '@lql-pg/verify': - specifier: workspace:* - version: link:../verify - devDependencies: - '@launchql/cli': - specifier: ^4.9.0 - version: 4.9.0(@babel/core@7.28.0)(@pgsql/types@17.6.1)(graphile-build-pg@4.14.1(graphql@15.5.2)(pg@8.16.3))(graphql@15.5.2)(pg-sql2@4.14.1(pg@8.16.3))(postgraphile-core@4.14.1(graphql@15.5.2)(pg@8.16.3)) - - packages/verify: devDependencies: '@launchql/cli': specifier: ^4.9.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0814391..4340350 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,2 @@ packages: - - 'packages/*' - - 'packages/security/*' - - 'packages/jobs/*' - - 'packages/data-types/*' - - 'packages/metrics/*' - - 'packages/utils/*' - - 'packages/meta/*' \ No newline at end of file + - 'packages/*' \ No newline at end of file From 1807d2b0b4117d3a8ce43666541ae0f0bf21827b Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 5 Oct 2025 15:47:46 -0700 Subject: [PATCH 5/5] cleanup roles and flow --- launchql.json | 2 +- packages/rls-demo/__tests__/rls.demo.test.ts | 24 +++-- .../rls-demo/__tests__/rls.inserts.pg.test.ts | 86 ++++++++++++++++++ .../rls-demo/__tests__/rls.inserts.test.ts | 89 +++++++++++++++++++ packages/rls-demo/deploy/rls-demo.sql | 22 ++++- 5 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 packages/rls-demo/__tests__/rls.inserts.pg.test.ts create mode 100644 packages/rls-demo/__tests__/rls.inserts.test.ts diff --git a/launchql.json b/launchql.json index 1db8c0d..47b7af8 100644 --- a/launchql.json +++ b/launchql.json @@ -7,7 +7,7 @@ "anonymous": "anon", "authenticated": "authenticated", "administrator": "service_role", - "default": "anonymous" + "default": "anon" } } } \ No newline at end of file diff --git a/packages/rls-demo/__tests__/rls.demo.test.ts b/packages/rls-demo/__tests__/rls.demo.test.ts index 9e2ac8c..1b17f58 100644 --- a/packages/rls-demo/__tests__/rls.demo.test.ts +++ b/packages/rls-demo/__tests__/rls.demo.test.ts @@ -38,27 +38,39 @@ describe('RLS Demo - Data Insertion', () => { ); // Insert products - const product1 = await pg.one( + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user1.id + }); + + const product1 = await db.one( `INSERT INTO rls_test.products (name, description, price, owner_id) VALUES ($1, $2, $3, $4) RETURNING id, name, price, owner_id`, ['Laptop Pro', 'High-performance laptop', 1299.99, user1.id] ); - const product2 = await pg.one( + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user2.id + }); + + const product2 = await db.one( `INSERT INTO rls_test.products (name, description, price, owner_id) VALUES ($1, $2, $3, $4) RETURNING id, name, price, owner_id`, - ['Wireless Mouse', 'Ergonomic mouse', 49.99, user1.id] + ['Wireless Mouse', 'Ergonomic mouse', 49.99, user2.id] ); expect(user1.email).toBe('alice@example.com'); expect(product1.name).toBe('Laptop Pro'); expect(product1.owner_id).toEqual(user1.id); + expect(product2.owner_id).toEqual(user2.id); + expect(product2.name).toBe('Wireless Mouse'); }); it('should query user products with joins', async () => { - const result = await pg.many( + const result = await db.many( `SELECT u.name, p.name as product_name, p.price FROM rls_test.users u JOIN rls_test.products p ON u.id = p.owner_id @@ -72,7 +84,7 @@ describe('RLS Demo - Data Insertion', () => { it('should test RLS context switching', async () => { // Get a user ID for context - const user = await pg.one(`SELECT id FROM rls_test.users LIMIT 1`); + const user = await db.one(`SELECT id FROM rls_test.users LIMIT 1`); // Set context to simulate authenticated user with JWT claims db.setContext({ @@ -99,7 +111,7 @@ describe('RLS Demo - Data Insertion', () => { it('should fail RLS when trying to access other user\'s data', async () => { // Get two different users - const users = await pg.many(`SELECT id FROM rls_test.users ORDER BY email LIMIT 2`); + const users = await db.many(`SELECT id FROM rls_test.users ORDER BY email LIMIT 2`); expect(users.length).toBeGreaterThanOrEqual(2); const user1 = users[0]; diff --git a/packages/rls-demo/__tests__/rls.inserts.pg.test.ts b/packages/rls-demo/__tests__/rls.inserts.pg.test.ts new file mode 100644 index 0000000..bd28e8b --- /dev/null +++ b/packages/rls-demo/__tests__/rls.inserts.pg.test.ts @@ -0,0 +1,86 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +let db: PgTestClient; +let pg: PgTestClient; +let teardown: () => Promise; + +beforeAll(async () => { + ({ db, pg, teardown } = await getConnections()); +}); + +afterAll(async () => { + await teardown(); +}); + +beforeEach(async () => { + await db.beforeEach(); +}); + +afterEach(async () => { + await db.afterEach(); +}); + +const user_id_1 = '550e8400-e29b-41d4-a716-446655440001'; +const user_id_2 = '550e8400-e29b-41d4-a716-446655440002'; + +describe('RLS Demo - Data Insertion', () => { + it('should insert users and products', async () => { + + // Insert users + await db.any( + `INSERT INTO rls_test.users (id, email, name) + VALUES ($1, $2, $3)`, + [user_id_1, 'alice@example.com', 'Alice Johnson'] + ); + + await db.any( + `INSERT INTO rls_test.users (id, email, name) + VALUES ($1, $2, $3)`, + [user_id_2, 'bob@example.com', 'Bob Smith'] + ); + + // Insert products + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user_id_1 + }); + + const product1 = await db.one( + `INSERT INTO rls_test.products (name, description, price, owner_id) + VALUES ($1, $2, $3, $4) + RETURNING id, name, price, owner_id`, + ['Laptop Pro', 'High-performance laptop', 1299.99, user_id_1] + ); + + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user_id_2 + }); + + const product2 = await db.one( + `INSERT INTO rls_test.products (name, description, price, owner_id) + VALUES ($1, $2, $3, $4) + RETURNING id, name, price, owner_id`, + ['Wireless Mouse', 'Ergonomic mouse', 49.99, user_id_2] + ); + + expect(product1.name).toBe('Laptop Pro'); + expect(product1.owner_id).toEqual(user_id_1); + expect(product2.owner_id).toEqual(user_id_2); + expect(product2.name).toBe('Wireless Mouse'); + }); + + it('should rollback to initial state', async () => { + db.setContext({ + role: 'service_role' + }); + + const result = await db.any( + `SELECT u.name, p.name as product_name, p.price + FROM rls_test.users u + JOIN rls_test.products p ON u.id = p.owner_id + ORDER BY u.name, p.name` + ); + expect(result.length).toEqual(0); + }); +}); diff --git a/packages/rls-demo/__tests__/rls.inserts.test.ts b/packages/rls-demo/__tests__/rls.inserts.test.ts new file mode 100644 index 0000000..22094d6 --- /dev/null +++ b/packages/rls-demo/__tests__/rls.inserts.test.ts @@ -0,0 +1,89 @@ +import { getConnections, PgTestClient } from 'pgsql-test'; + +let db: PgTestClient; +let teardown: () => Promise; + +beforeAll(async () => { + ({ db, teardown } = await getConnections()); +}); + +afterAll(async () => { + await teardown(); +}); + +beforeEach(async () => { + await db.beforeEach(); +}); + +afterEach(async () => { + await db.afterEach(); +}); + +describe('RLS Demo - Data Insertion', () => { + it('should insert users and products', async () => { + + db.setContext({ + role: 'service_role', + }); + + // Insert users + const user1 = await db.one( + `INSERT INTO rls_test.users (email, name) + VALUES ($1, $2) + RETURNING id, email, name`, + ['alice@example.com', 'Alice Johnson'] + ); + + const user2 = await db.one( + `INSERT INTO rls_test.users (email, name) + VALUES ($1, $2) + RETURNING id, email, name`, + ['bob@example.com', 'Bob Smith'] + ); + + // Insert products + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user1.id + }); + + const product1 = await db.one( + `INSERT INTO rls_test.products (name, description, price, owner_id) + VALUES ($1, $2, $3, $4) + RETURNING id, name, price, owner_id`, + ['Laptop Pro', 'High-performance laptop', 1299.99, user1.id] + ); + + db.setContext({ + role: 'authenticated', + 'jwt.claims.user_id': user2.id + }); + + const product2 = await db.one( + `INSERT INTO rls_test.products (name, description, price, owner_id) + VALUES ($1, $2, $3, $4) + RETURNING id, name, price, owner_id`, + ['Wireless Mouse', 'Ergonomic mouse', 49.99, user2.id] + ); + + expect(user1.email).toBe('alice@example.com'); + expect(product1.name).toBe('Laptop Pro'); + expect(product1.owner_id).toEqual(user1.id); + expect(product2.owner_id).toEqual(user2.id); + expect(product2.name).toBe('Wireless Mouse'); + }); + + it('should rollback to initial state', async () => { + db.setContext({ + role: 'service_role' + }); + + const result = await db.any( + `SELECT u.name, p.name as product_name, p.price + FROM rls_test.users u + JOIN rls_test.products p ON u.id = p.owner_id + ORDER BY u.name, p.name` + ); + expect(result.length).toEqual(0); + }); +}); diff --git a/packages/rls-demo/deploy/rls-demo.sql b/packages/rls-demo/deploy/rls-demo.sql index dec80a5..6b95d44 100644 --- a/packages/rls-demo/deploy/rls-demo.sql +++ b/packages/rls-demo/deploy/rls-demo.sql @@ -33,6 +33,22 @@ GRANT USAGE ON SCHEMA auth TO public; GRANT EXECUTE ON FUNCTION auth.uid() TO public; GRANT EXECUTE ON FUNCTION auth.role() TO public; + + + + + + + + + + + + + + + + -- Create rls_test schema CREATE SCHEMA IF NOT EXISTS rls_test; @@ -71,7 +87,7 @@ CREATE POLICY "Users can update own data" ON rls_test.users -- Users can insert their own data CREATE POLICY "Users can insert own data" ON rls_test.users - FOR INSERT WITH CHECK (auth.uid() = id); + FOR INSERT WITH CHECK (true); -- Users can delete their own data CREATE POLICY "Users can delete own data" ON rls_test.users @@ -94,6 +110,10 @@ CREATE POLICY "Users can update own products" ON rls_test.products CREATE POLICY "Users can delete own products" ON rls_test.products FOR DELETE USING (auth.uid() = owner_id); +-- Grant permissions to anon users +GRANT USAGE ON SCHEMA rls_test TO anon; +GRANT ALL ON rls_test.users TO anon; + -- Grant permissions to authenticated users GRANT USAGE ON SCHEMA rls_test TO authenticated; GRANT ALL ON rls_test.users TO authenticated;