From 133404ff729559a7bd7b42ffd6810ffe5ea521ae Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Mon, 13 May 2024 20:45:52 +0200 Subject: [PATCH 1/5] Added supabase auth to project This design is minimalist for now, it might be extended (at least with a basic profil page or command list) --- .gitignore | 4 + components/calendar/Sidebar.vue | 50 ++++---- components/calendar/state/yearly/Layout.vue | 2 +- components/global/user/CTA.vue | 77 ++++++++++++ components/ui/avatar/Avatar.vue | 21 ++++ components/ui/avatar/AvatarFallback.vue | 11 ++ components/ui/avatar/AvatarImage.vue | 9 ++ components/ui/avatar/index.ts | 24 ++++ components/ui/command/index.ts | 2 + nuxt.config.ts | 12 +- package.json | 3 +- pnpm-lock.yaml | 94 ++++++++++++++ supabase/config.toml | 128 ++++++++++++++++++++ supabase/migrations/202401_init.sql | 103 ++++++++++++++++ supabase/migrations/202402_auth_hook.sql | 58 +++++++++ supabase/seed.sql | 1 + 16 files changed, 568 insertions(+), 31 deletions(-) create mode 100644 components/global/user/CTA.vue create mode 100644 components/ui/avatar/Avatar.vue create mode 100644 components/ui/avatar/AvatarFallback.vue create mode 100644 components/ui/avatar/AvatarImage.vue create mode 100644 components/ui/avatar/index.ts create mode 100644 supabase/config.toml create mode 100644 supabase/migrations/202401_init.sql create mode 100644 supabase/migrations/202402_auth_hook.sql create mode 100644 supabase/seed.sql diff --git a/.gitignore b/.gitignore index 4a7f73a..6575dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ logs .env .env.* !.env.example + +# Supabase +.branches +.temp diff --git a/components/calendar/Sidebar.vue b/components/calendar/Sidebar.vue index df5fe1a..5320e60 100644 --- a/components/calendar/Sidebar.vue +++ b/components/calendar/Sidebar.vue @@ -2,55 +2,55 @@ import { useCalendar } from '@/stores/CalendarStore' import { PhList, PhHouse, PhMagnifyingGlass } from '@phosphor-icons/vue' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' -import Button from '../ui/button/Button.vue' const { revealAdvancedSearch } = useCalendar() - + - + - + - - - - + + + + - - - + + + Retourner aux outils - - - + + + - - - - + + + - - - + + + Recherche avancée - - - + + + + + diff --git a/components/calendar/state/yearly/Layout.vue b/components/calendar/state/yearly/Layout.vue index 48aa230..b513570 100644 --- a/components/calendar/state/yearly/Layout.vue +++ b/components/calendar/state/yearly/Layout.vue @@ -26,7 +26,7 @@ const moveCalendarRight = useThrottleFn(() => { - + +import { computed } from 'vue' + +import { PhUserCircle } from '@phosphor-icons/vue' + +const router = useRouter() + +const { auth } = useSupabaseClient() +const user = useSupabaseUser() +const userMeta = computed(() => user.value?.user_metadata) +const hasSession = computed(() => Boolean(user.value)) + +async function handleGoogleLogin() { + try { + auth.signInWithOAuth({ + provider: 'google', + options: { + queryParams: { + access_type: 'offline', + prompt: 'consent' + } + } + }) + } catch (err) { + console.log(err) + } +} + +async function handleLogout() { + try { + const { error } = await auth.signOut() + + if (error) throw error + } catch (err) { + console.log(err) + } +} + +function gotoProfilePage() { + router.push({ path: '/profil' }) +} + + + + + + + + + Me + + + + + + + + + + + Profil + Déconnexion + + + + Connexion + + + + + + + diff --git a/components/ui/avatar/Avatar.vue b/components/ui/avatar/Avatar.vue new file mode 100644 index 0000000..5b7b9fb --- /dev/null +++ b/components/ui/avatar/Avatar.vue @@ -0,0 +1,21 @@ + + + + + + + diff --git a/components/ui/avatar/AvatarFallback.vue b/components/ui/avatar/AvatarFallback.vue new file mode 100644 index 0000000..a671a21 --- /dev/null +++ b/components/ui/avatar/AvatarFallback.vue @@ -0,0 +1,11 @@ + + + + + + + diff --git a/components/ui/avatar/AvatarImage.vue b/components/ui/avatar/AvatarImage.vue new file mode 100644 index 0000000..43499fa --- /dev/null +++ b/components/ui/avatar/AvatarImage.vue @@ -0,0 +1,9 @@ + + + + + diff --git a/components/ui/avatar/index.ts b/components/ui/avatar/index.ts new file mode 100644 index 0000000..c4af1a6 --- /dev/null +++ b/components/ui/avatar/index.ts @@ -0,0 +1,24 @@ +import { type VariantProps, cva } from 'class-variance-authority' + +export { default as Avatar } from './Avatar.vue' +export { default as AvatarImage } from './AvatarImage.vue' +export { default as AvatarFallback } from './AvatarFallback.vue' + +export const avatarVariant = cva( + 'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden', + { + variants: { + size: { + sm: 'h-10 w-10 text-xs', + base: 'h-16 w-16 text-2xl', + lg: 'h-32 w-32 text-5xl', + }, + shape: { + circle: 'rounded-full', + square: 'rounded-md', + }, + }, + }, +) + +export type AvatarVariants = VariantProps diff --git a/components/ui/command/index.ts b/components/ui/command/index.ts index 0e35f4b..95d4b5f 100644 --- a/components/ui/command/index.ts +++ b/components/ui/command/index.ts @@ -1,3 +1,5 @@ +export { ComboboxPortal } from 'radix-vue' + export { default as Command } from './Command.vue' export { default as CommandDialog } from './CommandDialog.vue' export { default as CommandEmpty } from './CommandEmpty.vue' diff --git a/nuxt.config.ts b/nuxt.config.ts index 16b5fee..04956d5 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -3,12 +3,13 @@ export default defineNuxtConfig({ devtools: { enabled: true }, modules: [ - '@nuxtjs/tailwindcss', - 'shadcn-nuxt', - '@nuxtjs/color-mode', + '@nuxtjs/supabase', '@pinia/nuxt', + '@vueuse/nuxt', + '@nuxtjs/tailwindcss', + '@nuxtjs/color-mode', '@nuxt/eslint', - '@vueuse/nuxt' + 'shadcn-nuxt' ], css: ['~/assets/main.css'], @@ -27,5 +28,8 @@ export default defineNuxtConfig({ preference: 'dark', fallback: 'dark', }, + supabase: { + redirect: false + }, eslint: {} }) diff --git a/package.json b/package.json index a7c7372..9f01aad 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ }, "dependencies": { "@nuxt/eslint": "^0.3.12", - "@vueuse/nuxt": "^10.9.0", "@phosphor-icons/vue": "^2.2.1", "@pinia/nuxt": "^0.5.1", "@vueuse/core": "^10.9.0", + "@vueuse/nuxt": "^10.9.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-vue-next": "^0.364.0", @@ -32,6 +32,7 @@ }, "devDependencies": { "@nuxtjs/color-mode": "^3.4.1", + "@nuxtjs/supabase": "^1.2.2", "@nuxtjs/tailwindcss": "^6.12.0", "@stylistic/eslint-plugin-js": "^2.1.0", "@typescript-eslint/eslint-plugin": "^7.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3bfdd6b..d05267c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,9 @@ importers: '@nuxtjs/color-mode': specifier: ^3.4.1 version: 3.4.1(rollup@4.17.2) + '@nuxtjs/supabase': + specifier: ^1.2.2 + version: 1.2.2(rollup@4.17.2) '@nuxtjs/tailwindcss': specifier: ^6.12.0 version: 6.12.0(rollup@4.17.2) @@ -705,6 +708,9 @@ packages: '@nuxtjs/color-mode@3.4.1': resolution: {integrity: sha512-vZgJqDstxInGw3RGSWbLoCLXtU1mvh1LLeuEA/X3a++DYA4ifwSbNoiSiOyb9qZHFEwz1Xr99H71sXV4IhOaEg==} + '@nuxtjs/supabase@1.2.2': + resolution: {integrity: sha512-DNEuB7YXaOhV118bNway6SUpQLGq0zsK1kzddyETrwx3dHz+7etEw+QKU7iVs/GZog9Q7xkUQtGhcEQYaTZ71w==} + '@nuxtjs/tailwindcss@6.12.0': resolution: {integrity: sha512-vXvEq8z177TQcx0tc10mw3O6T9WeN0iTL8hIKGDfidmr+HKReexJU01aPgHefFrCu4LJB70egYFYnywzB9lMyQ==} @@ -1112,6 +1118,28 @@ packages: peerDependencies: eslint: '>=8.40.0' + '@supabase/auth-js@2.64.1': + resolution: {integrity: sha512-tA2PXLoWEzhD0N1Vysree+HftfeWBbFV0E+taND5rj/pZTjkwKq/9GlrnXkbs5pnw+tsnABDRo2WLZmymihGdA==} + + '@supabase/functions-js@2.3.1': + resolution: {integrity: sha512-QyzNle/rVzlOi4BbVqxLSH828VdGY1RElqGFAj+XeVypj6+PVtMlD21G8SDnsPQDtlqqTtoGRgdMlQZih5hTuw==} + + '@supabase/node-fetch@2.6.15': + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + + '@supabase/postgrest-js@1.15.2': + resolution: {integrity: sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==} + + '@supabase/realtime-js@2.9.5': + resolution: {integrity: sha512-TEHlGwNGGmKPdeMtca1lFTYCedrhTAv3nZVoSjrKQ+wkMmaERuCe57zkC5KSWFzLYkb5FVHW8Hrr+PX1DDwplQ==} + + '@supabase/storage-js@2.5.5': + resolution: {integrity: sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==} + + '@supabase/supabase-js@2.43.0': + resolution: {integrity: sha512-e/MXc/7HPEXayWoxDTv+xqxF5+wlpH3/+UoP3TXLH0OiShICTbyJfmRgn/GCNw7E7+DlUq+3c48q12FtGWrVmQ==} + '@swc/helpers@0.5.11': resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} @@ -1153,6 +1181,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/phoenix@1.6.4': + resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1162,6 +1193,9 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@types/ws@8.5.10': + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + '@typescript-eslint/eslint-plugin@7.8.0': resolution: {integrity: sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==} engines: {node: ^18.18.0 || >=20.0.0} @@ -5600,6 +5634,18 @@ snapshots: - rollup - supports-color + '@nuxtjs/supabase@1.2.2(rollup@4.17.2)': + dependencies: + '@nuxt/kit': 3.11.2(rollup@4.17.2) + '@supabase/supabase-js': 2.43.0 + defu: 6.1.4 + pathe: 1.1.2 + transitivePeerDependencies: + - bufferutil + - rollup + - supports-color + - utf-8-validate + '@nuxtjs/tailwindcss@6.12.0(rollup@4.17.2)': dependencies: '@nuxt/kit': 3.11.2(rollup@4.17.2) @@ -5987,6 +6033,48 @@ snapshots: - supports-color - typescript + '@supabase/auth-js@2.64.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/functions-js@2.3.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/node-fetch@2.6.15': + dependencies: + whatwg-url: 5.0.0 + + '@supabase/postgrest-js@1.15.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/realtime-js@2.9.5': + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.4 + '@types/ws': 8.5.10 + ws: 8.17.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.5.5': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/supabase-js@2.43.0': + dependencies: + '@supabase/auth-js': 2.64.1 + '@supabase/functions-js': 2.3.1 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.15.2 + '@supabase/realtime-js': 2.9.5 + '@supabase/storage-js': 2.5.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@swc/helpers@0.5.11': dependencies: tslib: 2.6.2 @@ -6026,12 +6114,18 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/phoenix@1.6.4': {} + '@types/resolve@1.20.2': {} '@types/semver@7.5.8': {} '@types/web-bluetooth@0.0.20': {} + '@types/ws@8.5.10': + dependencies: + '@types/node': 20.12.11 + '@typescript-eslint/eslint-plugin@7.8.0(@typescript-eslint/parser@7.8.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..c9aa090 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,128 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "leim-tools" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` is always included. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. `public` is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[storage.image_transformation] +enabled = true + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://localhost:5173/" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["http://localhost:5173/"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = false +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = false +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.google] +enabled = true +client_id = "env(GOOGLE_AUTH_ID)" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(GOOGLE_AUTH_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false + +[auth.hook.custom_access_token] +enabled = true +uri = "pg-functions://postgres/public/custom_access_token_hook" diff --git a/supabase/migrations/202401_init.sql b/supabase/migrations/202401_init.sql new file mode 100644 index 0000000..f12fe1c --- /dev/null +++ b/supabase/migrations/202401_init.sql @@ -0,0 +1,103 @@ +-- +-- For use with https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone +-- + +-- Custom types +create type public.app_permission as enum ('events.see.hidden'); +create type public.app_role as enum ('sa', 'gm'); + +-- USERS +create table public.users ( + id uuid references auth.users not null primary key, -- UUID from auth.users + username text +); +comment on table public.users is 'Profile data for each user.'; +comment on column public.users.id is 'References the internal Supabase Auth user.'; + +-- USER ROLES +create table public.user_roles ( + id bigint generated by default as identity primary key, + user_id uuid references public.users on delete cascade not null, + role app_role not null, + unique (user_id, role) +); +comment on table public.user_roles is 'Application roles for each user.'; + +-- ROLE PERMISSIONS +create table public.role_permissions ( + id bigint generated by default as identity primary key, + role app_role not null, + permission app_permission not null, + unique (role, permission) +); +comment on table public.role_permissions is 'Application permissions for each role.'; + +-- authorize with role-based access control (RBAC) +create function public.authorize( + requested_permission app_permission +) +returns boolean as $$ +declare + bind_permissions int; +begin + select count(*) + from public.role_permissions + where role_permissions.permission = authorize.requested_permission + and role_permissions.role = (auth.jwt() ->> 'user_role')::public.app_role + into bind_permissions; + + return bind_permissions > 0; +end; +$$ language plpgsql security definer set search_path = public; + +-- Secure the tables +alter table public.users enable row level security; +alter table public.user_roles enable row level security; +alter table public.role_permissions enable row level security; +create policy "Allow logged-in read access" on public.users for select using ( auth.role() = 'authenticated' ); +create policy "Allow individual insert access" on public.users for insert with check ( auth.uid() = id ); +create policy "Allow individual update access" on public.users for update using ( auth.uid() = id ); +create policy "Allow individual read access" on public.user_roles for select using ( auth.uid() = user_id ); + +-- Send "previous data" on change +alter table public.users replica identity full; + +-- inserts a row into public.users and assigns roles +create function public.handle_new_user() +returns trigger as $$ +declare is_admin boolean; +begin + insert into public.users (id, username) + values (new.id, new.email); + + return new; +end; +$$ language plpgsql security definer set search_path = auth, public; + +-- trigger the function every time a user is created +create trigger on_auth_user_created + after insert on auth.users + for each row execute procedure public.handle_new_user(); + +/** + * HELPER FUNCTIONS + * Create test user helper method. + */ +create or replace function public.create_user( + email text +) returns uuid + security definer + set search_path = auth +as $$ + declare + user_id uuid; +begin + user_id := extensions.uuid_generate_v4(); + + insert into auth.users (id, email) + values (user_id, email) + returning id into user_id; + + return user_id; +end; +$$ language plpgsql; diff --git a/supabase/migrations/202402_auth_hook.sql b/supabase/migrations/202402_auth_hook.sql new file mode 100644 index 0000000..2d6ef1e --- /dev/null +++ b/supabase/migrations/202402_auth_hook.sql @@ -0,0 +1,58 @@ +/** + * AUTH HOOKS + * Create an auth hook to add a custom claim to the access token jwt. + */ + +-- Create the auth hook function +-- https://supabase.com/docs/guides/auth/auth-hooks#hook-custom-access-token +create or replace function public.custom_access_token_hook(event jsonb) +returns jsonb +language plpgsql +stable +as $$ + declare + claims jsonb; + user_role public.app_role; + begin + -- Check if the user is marked as admin in the profiles table + select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid; + + claims := event->'claims'; + + if user_role is not null then + -- Set the claim + claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role)); + else + claims := jsonb_set(claims, '{user_role}', 'null'); + end if; + + -- Update the 'claims' object in the original event + event := jsonb_set(event, '{claims}', claims); + + -- Return the modified or original event + return event; + end; +$$; + +grant usage on schema public to supabase_auth_admin; + +grant execute + on function public.custom_access_token_hook + to supabase_auth_admin; + +revoke execute + on function public.custom_access_token_hook + from authenticated, anon; + +grant all + on table public.user_roles +to supabase_auth_admin; + +revoke all + on table public.user_roles + from authenticated, anon; + +create policy "Allow auth admin to read user roles" ON public.user_roles +as permissive for select +to supabase_auth_admin +using (true) diff --git a/supabase/seed.sql b/supabase/seed.sql new file mode 100644 index 0000000..c4008aa --- /dev/null +++ b/supabase/seed.sql @@ -0,0 +1 @@ +insert into public.role_permissions (role, permission) values ('gm', 'events.see.hidden'); From da6b30f00c1c74783abaefc11ca188c63b1cef56 Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Mon, 13 May 2024 22:10:48 +0200 Subject: [PATCH 2/5] Downgraded nuxt to prevent supabase errors The issues would happen randomly when login from google, it doesn't seem to happen again with nuxt 3.7.0 ? --- .npmrc | 3 +++ app.vue | 2 ++ components/global/user/CTA.vue | 38 +++++++++++++++++++--------------- package.json | 2 +- pnpm-lock.yaml | 2 +- 5 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..5bee2d4 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +# https://pnpm.io/npmrc#shamefully-hoist +# Fixes: WARN Failed to resolve dependency: @supabase/gotrue-js, present in 'optimizeDeps.include' +shamefully-hoist=true diff --git a/app.vue b/app.vue index ce4444b..ffcefe3 100644 --- a/app.vue +++ b/app.vue @@ -31,6 +31,8 @@ const useIdFunction = () => useId() + + diff --git a/components/global/user/CTA.vue b/components/global/user/CTA.vue index 692aeb8..da3558d 100644 --- a/components/global/user/CTA.vue +++ b/components/global/user/CTA.vue @@ -8,7 +8,13 @@ const router = useRouter() const { auth } = useSupabaseClient() const user = useSupabaseUser() const userMeta = computed(() => user.value?.user_metadata) -const hasSession = computed(() => Boolean(user.value)) + +const menuOpened = ref(false) + +function closeMenu() { + menuOpened.value = false +} +watch(user, closeMenu) async function handleGoogleLogin() { try { @@ -43,9 +49,9 @@ function gotoProfilePage() { - - - + + + - - - - - Profil - Déconnexion - - - - Connexion - - - + + + + Profil + Déconnexion + + + + Connexion + + diff --git a/package.json b/package.json index 9f01aad..d26d145 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-vue-next": "^0.364.0", - "nuxt": "^3.11.2", + "nuxt": "^3.7.0", "pinia": "^2.1.7", "radix-vue": "^1.7.4", "shadcn-nuxt": "^0.10.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d05267c..bb0846d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,7 +33,7 @@ importers: specifier: ^0.364.0 version: 0.364.0(vue@3.4.27(typescript@5.4.5)) nuxt: - specifier: ^3.11.2 + specifier: ^3.7.0 version: 3.11.2(@opentelemetry/api@1.8.0)(@parcel/watcher@2.4.1)(@types/node@20.12.11)(@unocss/reset@0.60.2)(encoding@0.1.13)(eslint@8.57.0)(floating-vue@5.2.2(@nuxt/kit@3.11.2(rollup@4.17.2))(vue@3.4.27(typescript@5.4.5)))(ioredis@5.4.1)(optionator@0.9.4)(rollup@4.17.2)(sass@1.77.1)(terser@5.31.0)(typescript@5.4.5)(unocss@0.60.2(postcss@8.4.38)(rollup@4.17.2)(vite@5.2.11(@types/node@20.12.11)(sass@1.77.1)(terser@5.31.0)))(vite@5.2.11(@types/node@20.12.11)(sass@1.77.1)(terser@5.31.0)) pinia: specifier: ^2.1.7 From 4034104c9e690d1b41be11c92f782ed0173fe5e7 Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Tue, 14 May 2024 11:21:08 +0200 Subject: [PATCH 3/5] Updated sidebar to be global --- components/{calendar => global}/Sidebar.vue | 33 +++++++++++++++++---- components/global/SidebarProps.ts | 10 +++++++ pages/calendar.vue | 17 +++++++---- 3 files changed, 49 insertions(+), 11 deletions(-) rename components/{calendar => global}/Sidebar.vue (60%) create mode 100644 components/global/SidebarProps.ts diff --git a/components/calendar/Sidebar.vue b/components/global/Sidebar.vue similarity index 60% rename from components/calendar/Sidebar.vue rename to components/global/Sidebar.vue index 5320e60..cbf174f 100644 --- a/components/calendar/Sidebar.vue +++ b/components/global/Sidebar.vue @@ -1,9 +1,8 @@ @@ -14,6 +13,7 @@ const { revealAdvancedSearch } = useCalendar() + @@ -30,7 +30,28 @@ const { revealAdvancedSearch } = useCalendar() - + + + + + + + + + + + + + + + + {{ item.tooltip }} + + + + + + diff --git a/components/global/SidebarProps.ts b/components/global/SidebarProps.ts new file mode 100644 index 0000000..fa40d08 --- /dev/null +++ b/components/global/SidebarProps.ts @@ -0,0 +1,10 @@ +export interface MenuItem { + phIcon: Component + tooltip: string + clickHandler?: () => void + to?: string +} + +export interface SidebarProps { + menuItems: MenuItem[] +} diff --git a/pages/calendar.vue b/pages/calendar.vue index c745ed4..0737a76 100644 --- a/pages/calendar.vue +++ b/pages/calendar.vue @@ -1,21 +1,28 @@ - + From cd5822def4b92567859e6b4b7bc83c8d87896f77 Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Tue, 14 May 2024 11:53:20 +0200 Subject: [PATCH 4/5] Profile page and sidebar layouts --- components/global/Heading.vue | 5 +++++ components/global/Sidebar.vue | 2 +- components/global/SidebarProps.ts | 3 ++- components/global/user/CTA.vue | 2 +- components/profile/Layout.vue | 9 ++++++++ pages/index.vue | 36 +++++++++++++++++++------------ pages/profile.vue | 17 +++++++++++++++ 7 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 components/global/Heading.vue create mode 100644 components/profile/Layout.vue create mode 100644 pages/profile.vue diff --git a/components/global/Heading.vue b/components/global/Heading.vue new file mode 100644 index 0000000..2726bcc --- /dev/null +++ b/components/global/Heading.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/components/global/Sidebar.vue b/components/global/Sidebar.vue index cbf174f..56142fb 100644 --- a/components/global/Sidebar.vue +++ b/components/global/Sidebar.vue @@ -14,7 +14,7 @@ defineProps() - + diff --git a/components/global/SidebarProps.ts b/components/global/SidebarProps.ts index fa40d08..adcfce0 100644 --- a/components/global/SidebarProps.ts +++ b/components/global/SidebarProps.ts @@ -6,5 +6,6 @@ export interface MenuItem { } export interface SidebarProps { - menuItems: MenuItem[] + menuItems: MenuItem[], + isHome?: boolean } diff --git a/components/global/user/CTA.vue b/components/global/user/CTA.vue index da3558d..72b76ee 100644 --- a/components/global/user/CTA.vue +++ b/components/global/user/CTA.vue @@ -43,7 +43,7 @@ async function handleLogout() { } function gotoProfilePage() { - router.push({ path: '/profil' }) + router.push({ path: '/profile' }) } diff --git a/components/profile/Layout.vue b/components/profile/Layout.vue new file mode 100644 index 0000000..623d0a6 --- /dev/null +++ b/components/profile/Layout.vue @@ -0,0 +1,9 @@ + + + + + {{ user?.user_metadata.full_name }} + + diff --git a/pages/index.vue b/pages/index.vue index e0c8f83..f4eb51d 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,23 +1,31 @@ - - - - Calendrier - - - chronologie - - - - - Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde - - + + + + + + + Calendrier + + + chronologie + + + + + Chronologie complète de Léïm, rassemblant les évènements et personnages clés du monde + + + diff --git a/pages/profile.vue b/pages/profile.vue new file mode 100644 index 0000000..54b45e8 --- /dev/null +++ b/pages/profile.vue @@ -0,0 +1,17 @@ + + + + + + + + + From f77950b5bee8eace5b9c041e9d653165245105b3 Mon Sep 17 00:00:00 2001 From: Alexis <35.alexis.pele@gmail.com> Date: Tue, 14 May 2024 12:07:05 +0200 Subject: [PATCH 5/5] Added auth guard middleware --- middleware/authGuard.ts | 7 +++++++ pages/profile.vue | 12 ++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 middleware/authGuard.ts diff --git a/middleware/authGuard.ts b/middleware/authGuard.ts new file mode 100644 index 0000000..b813363 --- /dev/null +++ b/middleware/authGuard.ts @@ -0,0 +1,7 @@ +export default defineNuxtRouteMiddleware(() => { + const user = useSupabaseUser() + + if (!user.value) { + return abortNavigation() + } +}) diff --git a/pages/profile.vue b/pages/profile.vue index 54b45e8..8f95e87 100644 --- a/pages/profile.vue +++ b/pages/profile.vue @@ -1,10 +1,22 @@
Retourner aux outils
Recherche avancée
{{ item.tooltip }}