diff --git a/deno.lock b/deno.lock index f51e275..a22b63f 100644 --- a/deno.lock +++ b/deno.lock @@ -23,6 +23,7 @@ "npm:@toon-format/toon@^2.1.0": "2.1.0", "npm:@types/bun@^1.3.5": "1.3.5", "npm:deno@^2.6.4": "2.6.4", + "npm:jwt-decode@4": "4.0.0", "npm:openai@6.16.0": "6.16.0_zod@3.25.76", "npm:openai@^4.52.5": "4.104.0_zod@3.25.76", "npm:openai@^6.16.0": "6.16.0_zod@3.25.76", @@ -419,6 +420,9 @@ "imurmurhash@0.1.4": { "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, + "jwt-decode@4.0.0": { + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, @@ -593,6 +597,7 @@ "npm:@toon-format/toon@^2.1.0", "npm:@types/bun@^1.3.5", "npm:deno@^2.6.4", + "npm:jwt-decode@4", "npm:openai@^6.16.0", "npm:supabase@^2.72.6" ] diff --git a/notebooks/supabase-security.ipynb b/notebooks/supabase-security.ipynb new file mode 100644 index 0000000..960d900 --- /dev/null +++ b/notebooks/supabase-security.ipynb @@ -0,0 +1,223 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "3b0acbc3", + "metadata": {}, + "outputs": [], + "source": [ + "import { createClient } from \"@supabase/supabase-js\";\n", + "const supabaseUrl = Deno.env.get(\"SUPABASE_URL\") || env.SUPABASE_URL;\n", + "const supabaseKey = Deno.env.get(\"SUPABASE_ANON_KEY\") || env.SUPABASE_ANON_KEY;\n", + "\n", + "const supabase = createClient(supabaseUrl, supabaseKey);\n", + "const supa = await supabase.auth.signInWithPassword({\n", + " email: 'guillermo.arrieta@lasalle.mx',\n", + " password: 'admin',\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ef7e79f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " access_token: \"eyJhbGciOiJFUzI1NiIsImtpZCI6ImI4MTI2OWYxLTIxZDgtNGYyZS1iNzE5LWMyMjQwYTg0MGQ5MCIsInR5cCI6IkpXVCJ9.eyJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MjQ3MzI3MX1dLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwiYXVkIjoiYXV0aGVudGljYXRlZCIsImVtYWlsIjoiZ3VpbGxlcm1vLmFycmlldGFAbGFzYWxsZS5teCIsImV4cCI6MTc3MjQ3Njg3MSwiZmVhdHVyZV94Ijp0cnVlLCJpYXQiOjE3NzI0NzMyNzEsImlzX2Fub255bW91cyI6ZmFsc2UsImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6NTQzMjEvYXV0aC92MSIsInBob25lIjoiIiwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJzZXNzaW9uX2lkIjoiZjc2ZjRkZGYtZTcxOS00NjRiLWIwYWUtODBhNmIxMjBlZmE0Iiwic3ViIjoiOTVkMWYzODEtNzAyMy00ZjMzLWJjNTQtMDhjY2QwOGFiZjQxIiwidGVuYW50X2lkIjoibG9jYWwtZGV2LXRlbmFudCIsInVzZXJfbWV0YWRhdGEiOnsiZW1haWxfdmVyaWZpZWQiOnRydWV9fQ.LEXrbBNetdCb8zfCiLcXhJg3EmX6XuE4cYOaFmYg1PhvvNI5XorbJQHhb4SQVggQDOauIqqkMRsK64GK1nheIw\",\n", + " token_type: \"bearer\",\n", + " expires_in: 3600,\n", + " expires_at: 1772476871,\n", + " refresh_token: \"lwgtrzw2tpzl\",\n", + " user: {\n", + " id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " aud: \"authenticated\",\n", + " role: \"authenticated\",\n", + " email: \"guillermo.arrieta@lasalle.mx\",\n", + " email_confirmed_at: \"2026-01-13T14:54:39.096101Z\",\n", + " phone: \"\",\n", + " confirmed_at: \"2026-01-13T14:54:39.096101Z\",\n", + " last_sign_in_at: \"2026-03-02T17:41:10.98658403Z\",\n", + " app_metadata: { provider: \"email\", providers: [ \"email\" ] },\n", + " user_metadata: { email_verified: true },\n", + " identities: [\n", + " {\n", + " identity_id: \"b2f3094d-a29b-42b5-be69-3397282ffcd7\",\n", + " id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " user_id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " identity_data: {\n", + " email: \"guillermo.arrieta@lasalle.mx\",\n", + " email_verified: false,\n", + " phone_verified: false,\n", + " sub: \"95d1f381-7023-4f33-bc54-08ccd08abf41\"\n", + " },\n", + " provider: \"email\",\n", + " last_sign_in_at: \"2026-01-13T14:54:39.085382Z\",\n", + " created_at: \"2026-01-13T14:54:39.08544Z\",\n", + " updated_at: \"2026-01-13T14:54:39.08544Z\",\n", + " email: \"guillermo.arrieta@lasalle.mx\"\n", + " }\n", + " ],\n", + " created_at: \"2026-01-13T14:54:39.081453Z\",\n", + " updated_at: \"2026-03-02T17:41:11.002577Z\",\n", + " is_anonymous: false\n", + " },\n", + " weak_password: {\n", + " message: \"Password should be at least 6 characters.\",\n", + " reasons: [ \"length\" ]\n", + " }\n", + "}\n" + ] + }, + { + "data": { + "text/plain": [ + "{\n", + " data: {\n", + " subscription: {\n", + " id: \u001b[32mSymbol(\"auth-callback\")\u001b[39m,\n", + " callback: \u001b[36m[AsyncFunction (anonymous)]\u001b[39m,\n", + " unsubscribe: \u001b[36m[Function: unsubscribe]\u001b[39m\n", + " }\n", + " }\n", + "}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import { jwtDecode } from \"npm:jwt-decode\";\n", + "\n", + "const listener = supabase.auth.onAuthStateChange(async (event, session) => {\n", + " if (session) {\n", + " const jwt = jwtDecode(session.access_token);\n", + " console.log(event, jwt);\n", + " // store in the session\n", + " localStorage.setItem(\"jwt\", JSON.stringify(session));\n", + " }\n", + "});\n", + "\n", + "listener;\n" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "0471a2d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " access_token: \"eyJhbGciOiJFUzI1NiIsImtpZCI6ImI4MTI2OWYxLTIxZDgtNGYyZS1iNzE5LWMyMjQwYTg0MGQ5MCIsInR5cCI6IkpXVCJ9.eyJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc3MjQ1OTQzMX1dLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwiYXVkIjoiYXV0aGVudGljYXRlZCIsImVtYWlsIjoiZ3VpbGxlcm1vLmFycmlldGFAbGFzYWxsZS5teCIsImV4cCI6MTc3MjQ3MjAwMywiZmVhdHVyZV94Ijp0cnVlLCJpYXQiOjE3NzI0Njg0MDMsImlzX2Fub255bW91cyI6ZmFsc2UsImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6NTQzMjEvYXV0aC92MSIsInBob25lIjoiIiwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJzZXNzaW9uX2lkIjoiMTJkNDc0OGUtNDBiMy00MzQ5LWI0MjUtMWFkNzM2YTk2NTM5Iiwic3ViIjoiOTVkMWYzODEtNzAyMy00ZjMzLWJjNTQtMDhjY2QwOGFiZjQxIiwidGVuYW50X2lkIjoibG9jYWwtZGV2LXRlbmFudCIsInVzZXJfbWV0YWRhdGEiOnsiZW1haWxfdmVyaWZpZWQiOnRydWV9fQ.XbxmaKPEo9Vqr1_W1vuXIaYwGiifaBsaqIwFdJvIuMrFPx5VeW9-NeUasWq3ka1DBsEMWaCFeIKykp9QlvDXqQ\",\n", + " token_type: \"bearer\",\n", + " expires_in: 3600,\n", + " expires_at: 1772472003,\n", + " refresh_token: \"gjdfzz4y4y3v\",\n", + " user: {\n", + " id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " aud: \"authenticated\",\n", + " role: \"authenticated\",\n", + " email: \"guillermo.arrieta@lasalle.mx\",\n", + " email_confirmed_at: \"2026-01-13T14:54:39.096101Z\",\n", + " phone: \"\",\n", + " confirmed_at: \"2026-01-13T14:54:39.096101Z\",\n", + " last_sign_in_at: \"2026-03-02T13:50:31.464916Z\",\n", + " app_metadata: { provider: \"email\", providers: [ \"email\" ] },\n", + " user_metadata: { email_verified: true },\n", + " identities: [\n", + " {\n", + " identity_id: \"b2f3094d-a29b-42b5-be69-3397282ffcd7\",\n", + " id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " user_id: \"95d1f381-7023-4f33-bc54-08ccd08abf41\",\n", + " identity_data: {\n", + " email: \"guillermo.arrieta@lasalle.mx\",\n", + " email_verified: false,\n", + " phone_verified: false,\n", + " sub: \"95d1f381-7023-4f33-bc54-08ccd08abf41\"\n", + " },\n", + " provider: \"email\",\n", + " last_sign_in_at: \"2026-01-13T14:54:39.085382Z\",\n", + " created_at: \"2026-01-13T14:54:39.08544Z\",\n", + " updated_at: \"2026-01-13T14:54:39.08544Z\",\n", + " email: \"guillermo.arrieta@lasalle.mx\"\n", + " }\n", + " ],\n", + " created_at: \"2026-01-13T14:54:39.081453Z\",\n", + " updated_at: \"2026-03-02T16:20:03.535209Z\",\n", + " is_anonymous: false\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "const raw = localStorage.getItem(\"jwt\")\n", + "const jwt = raw ? JSON.parse(raw) : null\n", + "\n", + "console.log(jwt)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "0bfcd412", + "metadata": {}, + "outputs": [ + { + "ename": "ReferenceError", + "evalue": "useEffect is not defined", + "output_type": "error", + "traceback": [ + "Stack trace:", + "ReferenceError: useEffect is not defined", + " at :1:22" + ] + } + ], + "source": [ + "useEffect(() => {\n", + " const onFocus = () => supabase.auth.refreshSession();\n", + " window.addEventListener(\"focus\", onFocus);\n", + " return () => window.removeEventListener(\"focus\", onFocus);\n", + "}, []);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88d2d6bc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Deno", + "language": "typescript", + "name": "deno" + }, + "language_info": { + "codemirror_mode": "typescript", + "file_extension": ".ts", + "mimetype": "text/x.typescript", + "name": "typescript", + "nbconvert_exporter": "script", + "pygments_lexer": "typescript", + "version": "5.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/package.json b/package.json index f5c50a5..6ca0e2b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@supabase/supabase-js": "^2.90.1", "@toon-format/toon": "^2.1.0", "deno": "^2.6.4", + "jwt-decode": "^4.0.0", "openai": "^6.16.0", "supabase": "^2.72.6" }, diff --git a/supabase/.temp/gotrue-version b/supabase/.temp/gotrue-version deleted file mode 100644 index 6d9afda..0000000 --- a/supabase/.temp/gotrue-version +++ /dev/null @@ -1 +0,0 @@ -v2.185.0 \ No newline at end of file diff --git a/supabase/.temp/pooler-url b/supabase/.temp/pooler-url deleted file mode 100644 index 53f64a6..0000000 --- a/supabase/.temp/pooler-url +++ /dev/null @@ -1 +0,0 @@ -postgresql://postgres.exdkssurzmjnnhgtiama@aws-0-us-west-1.pooler.supabase.com:5432/postgres \ No newline at end of file diff --git a/supabase/.temp/postgres-version b/supabase/.temp/postgres-version deleted file mode 100644 index ca1affb..0000000 --- a/supabase/.temp/postgres-version +++ /dev/null @@ -1 +0,0 @@ -15.8.1.085 \ No newline at end of file diff --git a/supabase/.temp/project-ref b/supabase/.temp/project-ref deleted file mode 100644 index a51b619..0000000 --- a/supabase/.temp/project-ref +++ /dev/null @@ -1 +0,0 @@ -exdkssurzmjnnhgtiama \ No newline at end of file diff --git a/supabase/.temp/rest-version b/supabase/.temp/rest-version deleted file mode 100644 index 2392826..0000000 --- a/supabase/.temp/rest-version +++ /dev/null @@ -1 +0,0 @@ -v12.2.3 \ No newline at end of file diff --git a/supabase/.temp/storage-migration b/supabase/.temp/storage-migration deleted file mode 100644 index 5a41722..0000000 --- a/supabase/.temp/storage-migration +++ /dev/null @@ -1 +0,0 @@ -buckets-objects-grants-postgres \ No newline at end of file diff --git a/supabase/.temp/storage-version b/supabase/.temp/storage-version deleted file mode 100644 index 89c19be..0000000 --- a/supabase/.temp/storage-version +++ /dev/null @@ -1 +0,0 @@ -v1.33.0 \ No newline at end of file diff --git a/supabase/config.toml b/supabase/config.toml index 98b01a5..373f6ca 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -48,3 +48,7 @@ entrypoint = "./functions/generate-subject-suggestions/index.ts" # Specifies static files to be bundled with the function. Supports glob patterns. # For example, if you want to serve static HTML pages in your function: # static_files = [ "./functions/generate-subject-suggestions/*.html" ] + +[auth.hook.custom_access_token] +enabled = true +uri = "pg-functions://postgres/public/custom_access_token_hook" \ No newline at end of file