@lastbrain/module-auth 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +533 -0
- package/dist/api/admin/users.d.ts +9 -0
- package/dist/api/admin/users.d.ts.map +1 -0
- package/dist/api/admin/users.js +38 -0
- package/dist/api/auth/me.d.ts +17 -0
- package/dist/api/auth/me.d.ts.map +1 -0
- package/dist/api/auth/me.js +32 -0
- package/dist/api/auth/profile.d.ts +32 -0
- package/dist/api/auth/profile.d.ts.map +1 -0
- package/dist/api/auth/profile.js +104 -0
- package/dist/api/public/signin.js +3 -3
- package/dist/api/storage.d.ts +13 -0
- package/dist/api/storage.d.ts.map +1 -0
- package/dist/api/storage.js +47 -0
- package/dist/auth.build.config.d.ts.map +1 -1
- package/dist/auth.build.config.js +42 -2
- package/dist/web/admin/users.d.ts.map +1 -1
- package/dist/web/admin/users.js +94 -2
- package/dist/web/auth/dashboard.d.ts +1 -1
- package/dist/web/auth/dashboard.d.ts.map +1 -1
- package/dist/web/auth/dashboard.js +42 -2
- package/dist/web/auth/profile.d.ts.map +1 -1
- package/dist/web/auth/profile.js +191 -2
- package/dist/web/auth/reglage.d.ts.map +1 -1
- package/dist/web/auth/reglage.js +98 -2
- package/dist/web/public/SignInPage.d.ts.map +1 -1
- package/dist/web/public/SignInPage.js +1 -1
- package/dist/web/public/SignUpPage.js +1 -1
- package/package.json +8 -7
- package/src/api/admin/users.ts +51 -0
- package/src/api/auth/me.ts +39 -0
- package/src/api/auth/profile.ts +142 -0
- package/src/api/public/signin.ts +3 -3
- package/src/api/storage.ts +66 -0
- package/src/auth.build.config.ts +42 -2
- package/src/web/admin/users.tsx +290 -1
- package/src/web/auth/dashboard.tsx +207 -1
- package/src/web/auth/profile.tsx +420 -1
- package/src/web/auth/reglage.tsx +284 -1
- package/src/web/public/SignInPage.tsx +1 -2
- package/src/web/public/SignUpPage.tsx +2 -2
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/migrations/20251112000000_user_init.sql +1 -1
- package/supabase/migrations/20251112000001_auto_profile_and_admin_view.sql +206 -0
- package/supabase/migrations/20251112000002_sync_avatars.sql +54 -0
- package/supabase/migrations-down/20251112000000_user_init.down.sql +2 -0
- package/supabase/migrations-down/20251112000001_auto_profile_and_admin_view.down.sql +23 -0
- package/supabase/migrations-down/20251112000002_sync_avatars.down.sql +9 -0
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Button,
|
|
9
9
|
Card,
|
|
10
10
|
CardBody,
|
|
11
|
-
CardHeader,
|
|
12
11
|
Chip,
|
|
13
12
|
Input,
|
|
14
13
|
Link,
|
|
@@ -22,7 +21,7 @@ function SignInForm() {
|
|
|
22
21
|
const [email, setEmail] = useState("");
|
|
23
22
|
const [password, setPassword] = useState("");
|
|
24
23
|
const [isLoading, setIsLoading] = useState(false);
|
|
25
|
-
const [error,
|
|
24
|
+
const [error, _setError] = useState<string | null>(null);
|
|
26
25
|
const redirectUrl = searchParams.get("redirect");
|
|
27
26
|
const router = useRouter();
|
|
28
27
|
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
@@ -75,7 +75,7 @@ function SignUpForm() {
|
|
|
75
75
|
// Si la confirmation par email est requise
|
|
76
76
|
if (data.user && !data.session) {
|
|
77
77
|
setSuccess(
|
|
78
|
-
"Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte."
|
|
78
|
+
"Compte créé avec succès ! Veuillez vérifier votre email pour confirmer votre compte.",
|
|
79
79
|
);
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
@@ -98,7 +98,7 @@ function SignUpForm() {
|
|
|
98
98
|
router.push("/signin");
|
|
99
99
|
}
|
|
100
100
|
}, 2000);
|
|
101
|
-
} catch
|
|
101
|
+
} catch {
|
|
102
102
|
addToast({
|
|
103
103
|
title: "Erreur",
|
|
104
104
|
description: "Une erreur inattendue est survenue.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v2.58.5
|
|
@@ -172,4 +172,4 @@ DROP TRIGGER IF EXISTS set_user_notifications_updated_at ON public.user_notifica
|
|
|
172
172
|
CREATE TRIGGER set_user_notifications_updated_at
|
|
173
173
|
BEFORE UPDATE ON public.user_notifications
|
|
174
174
|
FOR EACH ROW
|
|
175
|
-
EXECUTE FUNCTION public.set_user_notifications_updated_at();
|
|
175
|
+
EXECUTE FUNCTION public.set_user_notifications_updated_at();
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
-- Auto-create user profile when user signs up and admin RPC
|
|
2
|
+
-- Module: @lastbrain/module-auth
|
|
3
|
+
|
|
4
|
+
-- Add unique constraint on owner_id if not exists
|
|
5
|
+
DO $$
|
|
6
|
+
BEGIN
|
|
7
|
+
IF NOT EXISTS (
|
|
8
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
9
|
+
WHERE constraint_name = 'user_profil_owner_id_key'
|
|
10
|
+
AND table_name = 'user_profil'
|
|
11
|
+
) THEN
|
|
12
|
+
ALTER TABLE public.user_profil ADD CONSTRAINT user_profil_owner_id_key UNIQUE (owner_id);
|
|
13
|
+
END IF;
|
|
14
|
+
END $$;
|
|
15
|
+
|
|
16
|
+
-- Auto-create user profile when user signs up
|
|
17
|
+
-- This ensures every user has a profile in user_profil table
|
|
18
|
+
|
|
19
|
+
-- Function to create user profile automatically
|
|
20
|
+
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
21
|
+
RETURNS TRIGGER AS $$
|
|
22
|
+
BEGIN
|
|
23
|
+
INSERT INTO public.user_profil (owner_id, created_at, updated_at)
|
|
24
|
+
VALUES (NEW.id, now(), now());
|
|
25
|
+
RETURN NEW;
|
|
26
|
+
EXCEPTION
|
|
27
|
+
WHEN unique_violation THEN
|
|
28
|
+
-- Profile already exists, ignore
|
|
29
|
+
RETURN NEW;
|
|
30
|
+
END;
|
|
31
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
32
|
+
|
|
33
|
+
-- Trigger to call the function when a new user is created
|
|
34
|
+
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
35
|
+
CREATE TRIGGER on_auth_user_created
|
|
36
|
+
AFTER INSERT ON auth.users
|
|
37
|
+
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
|
38
|
+
|
|
39
|
+
-- =====================================================
|
|
40
|
+
-- Function: get_admin_users
|
|
41
|
+
-- =====================================================
|
|
42
|
+
-- RPC function for admins to get user data with emails
|
|
43
|
+
CREATE OR REPLACE FUNCTION public.get_admin_users(
|
|
44
|
+
page_number INTEGER DEFAULT 1,
|
|
45
|
+
page_size INTEGER DEFAULT 20,
|
|
46
|
+
search_term TEXT DEFAULT ''
|
|
47
|
+
)
|
|
48
|
+
RETURNS JSON
|
|
49
|
+
LANGUAGE plpgsql
|
|
50
|
+
SECURITY DEFINER
|
|
51
|
+
AS $$
|
|
52
|
+
DECLARE
|
|
53
|
+
offset_val INTEGER;
|
|
54
|
+
result JSON;
|
|
55
|
+
total_count INTEGER;
|
|
56
|
+
BEGIN
|
|
57
|
+
-- Check if user is superadmin
|
|
58
|
+
IF NOT is_superadmin(auth.uid()) THEN
|
|
59
|
+
RAISE EXCEPTION 'Access denied. Superadmin required.';
|
|
60
|
+
END IF;
|
|
61
|
+
|
|
62
|
+
offset_val := (page_number - 1) * page_size;
|
|
63
|
+
|
|
64
|
+
-- Get total count first
|
|
65
|
+
SELECT COUNT(*) INTO total_count
|
|
66
|
+
FROM public.user_profil p
|
|
67
|
+
LEFT JOIN auth.users au ON p.owner_id = au.id
|
|
68
|
+
WHERE
|
|
69
|
+
CASE
|
|
70
|
+
WHEN search_term = '' THEN true
|
|
71
|
+
ELSE (
|
|
72
|
+
p.first_name ILIKE '%' || search_term || '%' OR
|
|
73
|
+
p.last_name ILIKE '%' || search_term || '%' OR
|
|
74
|
+
au.email ILIKE '%' || search_term || '%' OR
|
|
75
|
+
(au.raw_user_meta_data->>'full_name') ILIKE '%' || search_term || '%' OR
|
|
76
|
+
(au.raw_app_meta_data->'roles'->>0) ILIKE '%' || search_term || '%'
|
|
77
|
+
)
|
|
78
|
+
END;
|
|
79
|
+
|
|
80
|
+
-- Build the result JSON
|
|
81
|
+
SELECT json_build_object(
|
|
82
|
+
'data', COALESCE(json_agg(
|
|
83
|
+
json_build_object(
|
|
84
|
+
'id', fp.owner_id,
|
|
85
|
+
'email', COALESCE(au.email, 'N/A'),
|
|
86
|
+
'created_at', COALESCE(au.created_at, fp.created_at),
|
|
87
|
+
'email_confirmed_at', au.email_confirmed_at,
|
|
88
|
+
'last_sign_in_at', au.last_sign_in_at,
|
|
89
|
+
'role', COALESCE(au.raw_app_meta_data->'roles'->>0, 'user'),
|
|
90
|
+
'full_name', au.raw_user_meta_data->>'full_name',
|
|
91
|
+
'avatar_path', au.raw_user_meta_data->>'avatar',
|
|
92
|
+
'metadata', au.raw_user_meta_data,
|
|
93
|
+
'profile', json_build_object(
|
|
94
|
+
'first_name', fp.first_name,
|
|
95
|
+
'last_name', fp.last_name,
|
|
96
|
+
'avatar_url', fp.avatar_url,
|
|
97
|
+
'bio', fp.bio,
|
|
98
|
+
'phone', fp.phone,
|
|
99
|
+
'company', fp.company,
|
|
100
|
+
'website', fp.website,
|
|
101
|
+
'location', fp.location,
|
|
102
|
+
'language', fp.language,
|
|
103
|
+
'timezone', fp.timezone
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
), '[]'::json),
|
|
107
|
+
'pagination', json_build_object(
|
|
108
|
+
'page', page_number,
|
|
109
|
+
'per_page', page_size,
|
|
110
|
+
'total', total_count,
|
|
111
|
+
'total_pages', CEIL(total_count::DECIMAL / page_size)
|
|
112
|
+
)
|
|
113
|
+
) INTO result
|
|
114
|
+
FROM (
|
|
115
|
+
SELECT
|
|
116
|
+
p.id,
|
|
117
|
+
p.owner_id,
|
|
118
|
+
p.first_name,
|
|
119
|
+
p.last_name,
|
|
120
|
+
p.avatar_url,
|
|
121
|
+
p.bio,
|
|
122
|
+
p.phone,
|
|
123
|
+
p.company,
|
|
124
|
+
p.website,
|
|
125
|
+
p.location,
|
|
126
|
+
p.language,
|
|
127
|
+
p.timezone,
|
|
128
|
+
p.created_at
|
|
129
|
+
FROM public.user_profil p
|
|
130
|
+
LEFT JOIN auth.users au ON p.owner_id = au.id
|
|
131
|
+
WHERE
|
|
132
|
+
CASE
|
|
133
|
+
WHEN search_term = '' THEN true
|
|
134
|
+
ELSE (
|
|
135
|
+
p.first_name ILIKE '%' || search_term || '%' OR
|
|
136
|
+
p.last_name ILIKE '%' || search_term || '%' OR
|
|
137
|
+
au.email ILIKE '%' || search_term || '%' OR
|
|
138
|
+
(au.raw_user_meta_data->>'full_name') ILIKE '%' || search_term || '%' OR
|
|
139
|
+
(au.raw_app_meta_data->'roles'->>0) ILIKE '%' || search_term || '%'
|
|
140
|
+
)
|
|
141
|
+
END
|
|
142
|
+
ORDER BY p.created_at DESC
|
|
143
|
+
LIMIT page_size
|
|
144
|
+
OFFSET offset_val
|
|
145
|
+
) fp
|
|
146
|
+
LEFT JOIN auth.users au ON fp.owner_id = au.id;
|
|
147
|
+
|
|
148
|
+
RETURN result;
|
|
149
|
+
END;
|
|
150
|
+
$$;
|
|
151
|
+
|
|
152
|
+
-- =====================================================
|
|
153
|
+
-- Function: sync_fullname_to_metadata
|
|
154
|
+
-- =====================================================
|
|
155
|
+
-- Synchronize full_name in auth.users metadata when profile is updated
|
|
156
|
+
CREATE OR REPLACE FUNCTION public.sync_fullname_to_metadata()
|
|
157
|
+
RETURNS TRIGGER AS $$
|
|
158
|
+
DECLARE
|
|
159
|
+
full_name_value TEXT;
|
|
160
|
+
current_metadata JSONB;
|
|
161
|
+
BEGIN
|
|
162
|
+
-- Build full_name from first_name and last_name
|
|
163
|
+
full_name_value := TRIM(CONCAT(COALESCE(NEW.first_name, ''), ' ', COALESCE(NEW.last_name, '')));
|
|
164
|
+
|
|
165
|
+
-- If full_name is empty or just spaces, set to null
|
|
166
|
+
IF full_name_value = '' OR full_name_value IS NULL THEN
|
|
167
|
+
full_name_value := NULL;
|
|
168
|
+
END IF;
|
|
169
|
+
|
|
170
|
+
-- Get current metadata
|
|
171
|
+
SELECT COALESCE(raw_user_meta_data, '{}'::jsonb)
|
|
172
|
+
INTO current_metadata
|
|
173
|
+
FROM auth.users
|
|
174
|
+
WHERE id = NEW.owner_id;
|
|
175
|
+
|
|
176
|
+
-- Update metadata with new full_name
|
|
177
|
+
UPDATE auth.users
|
|
178
|
+
SET raw_user_meta_data = current_metadata || jsonb_build_object('full_name', full_name_value)
|
|
179
|
+
WHERE id = NEW.owner_id;
|
|
180
|
+
|
|
181
|
+
RETURN NEW;
|
|
182
|
+
END;
|
|
183
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
184
|
+
|
|
185
|
+
-- Trigger to sync full_name when profile is updated
|
|
186
|
+
DROP TRIGGER IF EXISTS sync_fullname_on_profile_update ON public.user_profil;
|
|
187
|
+
CREATE TRIGGER sync_fullname_on_profile_update
|
|
188
|
+
AFTER UPDATE OF first_name, last_name ON public.user_profil
|
|
189
|
+
FOR EACH ROW
|
|
190
|
+
WHEN (OLD.first_name IS DISTINCT FROM NEW.first_name OR OLD.last_name IS DISTINCT FROM NEW.last_name)
|
|
191
|
+
EXECUTE FUNCTION public.sync_fullname_to_metadata();
|
|
192
|
+
|
|
193
|
+
-- Trigger to sync full_name when profile is inserted
|
|
194
|
+
DROP TRIGGER IF EXISTS sync_fullname_on_profile_insert ON public.user_profil;
|
|
195
|
+
CREATE TRIGGER sync_fullname_on_profile_insert
|
|
196
|
+
AFTER INSERT ON public.user_profil
|
|
197
|
+
FOR EACH ROW
|
|
198
|
+
WHEN (NEW.first_name IS NOT NULL OR NEW.last_name IS NOT NULL)
|
|
199
|
+
EXECUTE FUNCTION public.sync_fullname_to_metadata();
|
|
200
|
+
|
|
201
|
+
-- Create profile for existing users who don't have one (if any)
|
|
202
|
+
INSERT INTO public.user_profil (owner_id, created_at, updated_at)
|
|
203
|
+
SELECT id, created_at, created_at
|
|
204
|
+
FROM auth.users
|
|
205
|
+
WHERE id NOT IN (SELECT owner_id FROM public.user_profil WHERE owner_id IS NOT NULL)
|
|
206
|
+
ON CONFLICT (owner_id) DO NOTHING;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- Sync avatar_url from auth.users metadata to user_profil
|
|
2
|
+
-- Module: @lastbrain/module-auth
|
|
3
|
+
|
|
4
|
+
-- Function to sync avatars from auth metadata to user_profil
|
|
5
|
+
CREATE OR REPLACE FUNCTION public.sync_avatar_from_auth_metadata()
|
|
6
|
+
RETURNS VOID
|
|
7
|
+
LANGUAGE plpgsql
|
|
8
|
+
SECURITY DEFINER
|
|
9
|
+
AS $$
|
|
10
|
+
BEGIN
|
|
11
|
+
-- Update user_profil with avatar URLs from auth.users metadata
|
|
12
|
+
UPDATE public.user_profil
|
|
13
|
+
SET avatar_url = CASE
|
|
14
|
+
WHEN (au.raw_user_meta_data->>'avatar') IS NOT NULL
|
|
15
|
+
THEN concat('/', au.raw_user_meta_data->>'avatar')
|
|
16
|
+
ELSE avatar_url
|
|
17
|
+
END
|
|
18
|
+
FROM auth.users au
|
|
19
|
+
WHERE public.user_profil.owner_id = au.id
|
|
20
|
+
AND (au.raw_user_meta_data->>'avatar') IS NOT NULL
|
|
21
|
+
AND public.user_profil.avatar_url IS NULL;
|
|
22
|
+
|
|
23
|
+
-- Log the number of updated records
|
|
24
|
+
RAISE NOTICE 'Avatar sync completed';
|
|
25
|
+
END;
|
|
26
|
+
$$;
|
|
27
|
+
|
|
28
|
+
-- Create a trigger to automatically sync avatar when auth.users is updated
|
|
29
|
+
CREATE OR REPLACE FUNCTION public.handle_auth_user_avatar_update()
|
|
30
|
+
RETURNS TRIGGER AS $$
|
|
31
|
+
BEGIN
|
|
32
|
+
-- Only update if the avatar metadata changed
|
|
33
|
+
IF (OLD.raw_user_meta_data->>'avatar') IS DISTINCT FROM (NEW.raw_user_meta_data->>'avatar') THEN
|
|
34
|
+
UPDATE public.user_profil
|
|
35
|
+
SET avatar_url = CASE
|
|
36
|
+
WHEN (NEW.raw_user_meta_data->>'avatar') IS NOT NULL
|
|
37
|
+
THEN concat('/', NEW.raw_user_meta_data->>'avatar')
|
|
38
|
+
ELSE NULL
|
|
39
|
+
END
|
|
40
|
+
WHERE owner_id = NEW.id;
|
|
41
|
+
END IF;
|
|
42
|
+
|
|
43
|
+
RETURN NEW;
|
|
44
|
+
END;
|
|
45
|
+
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
46
|
+
|
|
47
|
+
-- Create trigger for avatar sync on auth.users update
|
|
48
|
+
DROP TRIGGER IF EXISTS on_auth_user_avatar_updated ON auth.users;
|
|
49
|
+
CREATE TRIGGER on_auth_user_avatar_updated
|
|
50
|
+
AFTER UPDATE ON auth.users
|
|
51
|
+
FOR EACH ROW EXECUTE FUNCTION public.handle_auth_user_avatar_update();
|
|
52
|
+
|
|
53
|
+
-- Run initial sync for existing users
|
|
54
|
+
SELECT public.sync_avatar_from_auth_metadata();
|
|
@@ -40,6 +40,8 @@ DROP TABLE IF EXISTS public.user_address;
|
|
|
40
40
|
-- =====================================================
|
|
41
41
|
DROP TRIGGER IF EXISTS set_user_profil_updated_at ON public.user_profil;
|
|
42
42
|
DROP FUNCTION IF EXISTS public.set_user_profil_updated_at();
|
|
43
|
+
DROP FUNCTION IF EXISTS public.handle_new_user();
|
|
44
|
+
|
|
43
45
|
|
|
44
46
|
DROP POLICY IF EXISTS user_profil_superadmin_all ON public.user_profil;
|
|
45
47
|
DROP POLICY IF EXISTS user_profil_owner_delete ON public.user_profil;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Rollback auto-create user profile and admin RPC
|
|
2
|
+
-- Module: @lastbrain/module-auth
|
|
3
|
+
|
|
4
|
+
-- Drop the RPC function
|
|
5
|
+
DROP FUNCTION IF EXISTS public.get_admin_users(INTEGER, INTEGER, TEXT);
|
|
6
|
+
|
|
7
|
+
-- Drop the trigger first
|
|
8
|
+
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
9
|
+
|
|
10
|
+
-- Drop the function
|
|
11
|
+
DROP FUNCTION IF EXISTS public.handle_new_user() CASCADE;
|
|
12
|
+
|
|
13
|
+
-- Remove unique constraint on owner_id if exists
|
|
14
|
+
DO $$
|
|
15
|
+
BEGIN
|
|
16
|
+
IF EXISTS (
|
|
17
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
18
|
+
WHERE constraint_name = 'user_profil_owner_id_key'
|
|
19
|
+
AND table_name = 'user_profil'
|
|
20
|
+
) THEN
|
|
21
|
+
ALTER TABLE public.user_profil DROP CONSTRAINT user_profil_owner_id_key;
|
|
22
|
+
END IF;
|
|
23
|
+
END $$;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Rollback avatar sync functionality
|
|
2
|
+
-- Module: @lastbrain/module-auth
|
|
3
|
+
|
|
4
|
+
-- Drop the trigger
|
|
5
|
+
DROP TRIGGER IF EXISTS on_auth_user_avatar_updated ON auth.users;
|
|
6
|
+
|
|
7
|
+
-- Drop the functions
|
|
8
|
+
DROP FUNCTION IF EXISTS public.handle_auth_user_avatar_update();
|
|
9
|
+
DROP FUNCTION IF EXISTS public.sync_avatar_from_auth_metadata();
|