@plutocms/supabase 0.11.2 → 0.12.0
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/.env.example +14 -0
- package/CHANGELOG.md +14 -0
- package/FEATURES.md +5 -1
- package/README.md +66 -0
- package/app/components/access/CopyButton.vue +24 -0
- package/app/components/access/DeleteRoleModal.vue +73 -0
- package/app/components/access/EditRolesSlideover.vue +134 -0
- package/app/components/access/InviteModal.vue +288 -0
- package/app/components/access/LinkField.vue +19 -0
- package/app/components/access/LinkModal.vue +29 -0
- package/app/components/access/RevokeModal.vue +83 -0
- package/app/components/access/RoleSlideover.vue +251 -0
- package/app/components/mail/AuthEmailsPanel.vue +108 -0
- package/app/pages/admin/(auth)/accept-invite.vue +155 -0
- package/app/pages/admin/settings/roles.vue +187 -0
- package/app/pages/admin/settings/users.vue +372 -0
- package/app/plugins/pluto-extension.ts +38 -1
- package/app/utils/access-ui.ts +100 -0
- package/app/utils/auth-driver.ts +3 -0
- package/app/utils/invite-link.ts +70 -0
- package/app/utils/supabase-project.ts +54 -0
- package/db/migrations/008_users_and_roles_admin.sql +462 -0
- package/i18n/locales/en.json +194 -1
- package/i18n/locales/pt-BR.json +194 -1
- package/package.json +3 -2
- package/server/api/access/capabilities.get.ts +17 -0
- package/server/api/access/roles/[key].delete.ts +18 -0
- package/server/api/access/roles/[key].put.ts +26 -0
- package/server/api/access/roles.get.ts +17 -0
- package/server/api/access/roles.post.ts +25 -0
- package/server/api/access/users/[id]/resend.post.ts +172 -0
- package/server/api/access/users/[id]/roles.delete.ts +22 -0
- package/server/api/access/users/[id]/roles.put.ts +24 -0
- package/server/api/access/users/invite-link.post.ts +50 -0
- package/server/api/access/users/invite.post.ts +201 -0
- package/server/api/access/users.get.ts +17 -0
- package/server/api/permissions/me.get.ts +15 -9
- package/server/utils/access-errors.ts +148 -0
- package/server/utils/access-invite.ts +66 -0
- package/server/utils/access-mail.ts +354 -0
- package/server/utils/admin-guard.ts +4 -3
- package/server/utils/admin-role-guard.ts +44 -0
- package/server/utils/rate-limit.ts +84 -0
- package/server/utils/service-client.ts +18 -0
- package/server/utils/sign-in-link.ts +129 -0
- package/shared/types/access.d.ts +41 -0
- package/shared/types/supabase.ts +50 -9
package/i18n/locales/en.json
CHANGED
|
@@ -87,6 +87,14 @@
|
|
|
87
87
|
"verificationSent": "A verification link has been sent to your email. Please check your inbox and follow the instructions to verify your account.",
|
|
88
88
|
"closePage": "You can close this page. You will be logged in automatically once you verify your email.",
|
|
89
89
|
"checkSpam": "If you don't see the email, please check your spam folder."
|
|
90
|
+
},
|
|
91
|
+
"invite": {
|
|
92
|
+
"pageTitle": "Accept Invite",
|
|
93
|
+
"title": "Welcome!",
|
|
94
|
+
"description": "Choose a password to finish your account.",
|
|
95
|
+
"submit": "Save password and continue",
|
|
96
|
+
"expired": "This invite link has expired or was already used. Ask an admin to send a new invite.",
|
|
97
|
+
"invalid": "This invite link is not valid. Open the link from your invite email again, or ask an admin for a new invite."
|
|
90
98
|
}
|
|
91
99
|
},
|
|
92
100
|
"settings": {
|
|
@@ -102,7 +110,8 @@
|
|
|
102
110
|
},
|
|
103
111
|
"capabilities": {
|
|
104
112
|
"settingsManage": "Manage site settings",
|
|
105
|
-
"usersRead": "View all user accounts"
|
|
113
|
+
"usersRead": "View all user accounts",
|
|
114
|
+
"group": "Supabase"
|
|
106
115
|
},
|
|
107
116
|
"envWarning": {
|
|
108
117
|
"title": "Connection string not saved automatically",
|
|
@@ -188,6 +197,190 @@
|
|
|
188
197
|
"loadFailed": "Could not load migration status",
|
|
189
198
|
"loadFailedDescription": "This isn't the dev-server-restart case above — something else went wrong reading migration status. Check the server console, then try again.",
|
|
190
199
|
"retry": "Retry"
|
|
200
|
+
},
|
|
201
|
+
"access": {
|
|
202
|
+
"users": {
|
|
203
|
+
"title": "Users",
|
|
204
|
+
"description": "The people who can use the admin. Customers are not listed here.",
|
|
205
|
+
"invite": "Invite user",
|
|
206
|
+
"you": "You",
|
|
207
|
+
"administrator": "Administrator",
|
|
208
|
+
"neverSignedIn": "Never",
|
|
209
|
+
"actionsLabel": "Actions for {name}",
|
|
210
|
+
"columns": {
|
|
211
|
+
"user": "User",
|
|
212
|
+
"roles": "Roles",
|
|
213
|
+
"status": "Status",
|
|
214
|
+
"lastSignIn": "Last sign-in"
|
|
215
|
+
},
|
|
216
|
+
"status": {
|
|
217
|
+
"active": "Active",
|
|
218
|
+
"invited": "Invited",
|
|
219
|
+
"invitedOn": "Invited {date}"
|
|
220
|
+
},
|
|
221
|
+
"menu": {
|
|
222
|
+
"editRoles": "Edit roles",
|
|
223
|
+
"resend": "Resend invite",
|
|
224
|
+
"copyLink": "Copy invite link",
|
|
225
|
+
"revoke": "Revoke access"
|
|
226
|
+
},
|
|
227
|
+
"empty": {
|
|
228
|
+
"title": "No users yet",
|
|
229
|
+
"description": "Invite the first person to help manage this site."
|
|
230
|
+
},
|
|
231
|
+
"error": {
|
|
232
|
+
"title": "The users could not be loaded"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"invite": {
|
|
236
|
+
"title": "Invite user",
|
|
237
|
+
"description": "The person gets an email with a link to choose a password. The roles apply when they sign in.",
|
|
238
|
+
"email": "Email",
|
|
239
|
+
"emailPlaceholder": "name@example.com",
|
|
240
|
+
"name": "Name (optional)",
|
|
241
|
+
"namePlaceholder": "e.g. Ada Lovelace",
|
|
242
|
+
"roles": "Roles",
|
|
243
|
+
"rolesRequired": "Pick at least one role.",
|
|
244
|
+
"locale": "Email language",
|
|
245
|
+
"localeHint": "The language of the invite email.",
|
|
246
|
+
"submit": "Send invite",
|
|
247
|
+
"mailProblem": "The invite email was not sent",
|
|
248
|
+
"copyLink": "Copy invite link",
|
|
249
|
+
"setupEmail": "Set up email",
|
|
250
|
+
"needsKey": "To copy the invite link instead, set NUXT_SUPABASE_SECRET_KEY on the server.",
|
|
251
|
+
"linkTitle": "The invite is ready",
|
|
252
|
+
"linkDescription": "The user was added. Send them this link yourself.",
|
|
253
|
+
"done": "Done"
|
|
254
|
+
},
|
|
255
|
+
"link": {
|
|
256
|
+
"label": "Invite link",
|
|
257
|
+
"note": "This link signs the person in once, then it expires. Send it only to them.",
|
|
258
|
+
"copy": "Copy link",
|
|
259
|
+
"copied": "Link copied",
|
|
260
|
+
"title": "Invite link"
|
|
261
|
+
},
|
|
262
|
+
"editRoles": {
|
|
263
|
+
"title": "Edit roles",
|
|
264
|
+
"description": "Choose the roles of {name}.",
|
|
265
|
+
"save": "Save roles",
|
|
266
|
+
"emptyHint": "Pick at least one role. To remove all access, use Revoke access.",
|
|
267
|
+
"selfAdmin": "You are removing your own admin role. You will lose access to Users and Roles."
|
|
268
|
+
},
|
|
269
|
+
"revoke": {
|
|
270
|
+
"title": "Revoke access",
|
|
271
|
+
"body": "{name} will lose every role and cannot use the admin. The account stays. Only the admin access is removed.",
|
|
272
|
+
"confirm": "Revoke access",
|
|
273
|
+
"self": "This is your own account. You will lose access to the admin."
|
|
274
|
+
},
|
|
275
|
+
"roles": {
|
|
276
|
+
"title": "Roles",
|
|
277
|
+
"description": "A role is a set of permissions. Give roles to users to control what they can do.",
|
|
278
|
+
"create": "New role",
|
|
279
|
+
"builtin": "Built-in",
|
|
280
|
+
"fullAccess": "Full access",
|
|
281
|
+
"permissionCount": "{count} permission | {count} permissions",
|
|
282
|
+
"userCount": "{count} user | {count} users",
|
|
283
|
+
"columns": {
|
|
284
|
+
"role": "Role",
|
|
285
|
+
"permissions": "Permissions",
|
|
286
|
+
"users": "Users"
|
|
287
|
+
},
|
|
288
|
+
"menu": {
|
|
289
|
+
"edit": "Edit",
|
|
290
|
+
"view": "View",
|
|
291
|
+
"delete": "Delete"
|
|
292
|
+
},
|
|
293
|
+
"error": {
|
|
294
|
+
"title": "The roles could not be loaded"
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"role": {
|
|
298
|
+
"titleCreate": "New role",
|
|
299
|
+
"titleEdit": "Edit role",
|
|
300
|
+
"titleView": "Role",
|
|
301
|
+
"description": "Choose what this role can do.",
|
|
302
|
+
"label": "Name",
|
|
303
|
+
"labelPlaceholder": "e.g. Editor",
|
|
304
|
+
"key": "Key",
|
|
305
|
+
"keyHint": "Use a-z, 0-9 and an underscore. You cannot change the key later.",
|
|
306
|
+
"keyInvalid": "The key can use only a-z, 0-9 and an underscore.",
|
|
307
|
+
"about": "Description",
|
|
308
|
+
"aboutPlaceholder": "What is this role for?",
|
|
309
|
+
"capabilities": "Permissions",
|
|
310
|
+
"capabilitiesRequired": "Pick at least one permission.",
|
|
311
|
+
"fullAccessNote": "This role has full access. It cannot be changed.",
|
|
312
|
+
"save": "Save role"
|
|
313
|
+
},
|
|
314
|
+
"deleteRole": {
|
|
315
|
+
"title": "Delete role",
|
|
316
|
+
"body": "Delete the role {name}? This cannot be undone.",
|
|
317
|
+
"usersLose": "No user has this role. | 1 user loses this role. | {count} users lose this role.",
|
|
318
|
+
"confirm": "Delete role"
|
|
319
|
+
},
|
|
320
|
+
"toasts": {
|
|
321
|
+
"invited": "Invite sent to {email}.",
|
|
322
|
+
"granted": "{email} already has an account. The roles are granted.",
|
|
323
|
+
"noLinkConfirmed": "This account is confirmed. It signs in with its own password, so it needs no link.",
|
|
324
|
+
"emailFailed": "{email} was added, but the email did not send. Copy the invite link and send it yourself.",
|
|
325
|
+
"resent": "Invite sent again to {email}.",
|
|
326
|
+
"resentNoEmail": "The email did not send. Copy the invite link and send it yourself.",
|
|
327
|
+
"rolesSaved": "Roles saved.",
|
|
328
|
+
"revoked": "Access revoked.",
|
|
329
|
+
"roleCreated": "Role created.",
|
|
330
|
+
"roleSaved": "Role saved.",
|
|
331
|
+
"roleDeleted": "Role deleted.",
|
|
332
|
+
"failed": "That did not work"
|
|
333
|
+
},
|
|
334
|
+
"errors": {
|
|
335
|
+
"forbidden": "Only an admin can do this.",
|
|
336
|
+
"lastAdmin": "This is the last admin. Make another user an admin first.",
|
|
337
|
+
"roleExists": "A role with this key already exists.",
|
|
338
|
+
"builtinRole": "A built-in role cannot be changed or deleted.",
|
|
339
|
+
"wildcard": "A new role cannot hold the wildcard permission.",
|
|
340
|
+
"emptyRole": "A role needs at least one permission.",
|
|
341
|
+
"unknownRole": "One of the roles does not exist.",
|
|
342
|
+
"invalidKey": "The role key can use only a-z, 0-9 and an underscore.",
|
|
343
|
+
"invalidLabel": "The role needs a name.",
|
|
344
|
+
"invalidCapability": "One of the permissions is not valid.",
|
|
345
|
+
"userNotFound": "This user does not exist.",
|
|
346
|
+
"roleNotFound": "This role does not exist.",
|
|
347
|
+
"invalidUserId": "The user id is not valid.",
|
|
348
|
+
"signupDisabled": "Public sign-up is off. Set NUXT_SUPABASE_SECRET_KEY on the server to send invites.",
|
|
349
|
+
"emailNotAuthorized": "Supabase's built-in email sends only to members of your Supabase organization. Set up custom SMTP in Supabase to invite other addresses.",
|
|
350
|
+
"emailInvalid": "Supabase does not accept this email address. Use a real address: Supabase refuses example and test domains.",
|
|
351
|
+
"inviteFailed": "The invite email could not be sent.",
|
|
352
|
+
"rateLimited": "Too many invites. Wait a few minutes and try again.",
|
|
353
|
+
"emailRateLimited": "Supabase sent too many emails. Wait a minute for the same address, or up to an hour with the built-in mailer. Custom SMTP in Supabase raises this limit.",
|
|
354
|
+
"alreadyAccepted": "This user already accepted the invite.",
|
|
355
|
+
"migrationPending": "Apply the pending database migrations to manage users and roles.",
|
|
356
|
+
"serviceKeyRequired": "Set NUXT_SUPABASE_SECRET_KEY on the server to make invite links."
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"mail": {
|
|
360
|
+
"authEmails": {
|
|
361
|
+
"title": "Supabase sign-in emails",
|
|
362
|
+
"description": "Sign-up, password reset and magic link emails.",
|
|
363
|
+
"intro": "Supabase sends these emails, not Pluto. Without custom SMTP, Supabase sends them only to the members of your Supabase team, and only a few each hour. Invites from the Users page use Pluto mail when the service key is set.",
|
|
364
|
+
"resendTitle": "Connect Resend to Supabase",
|
|
365
|
+
"resendBody": "Resend can fill in the SMTP settings of your Supabase project for you. You sign in to both services. Pluto keeps no key.",
|
|
366
|
+
"resendLink": "Open the Resend integration",
|
|
367
|
+
"manualTitle": "Or enter the SMTP values by hand",
|
|
368
|
+
"manualBody": "In Supabase, open Authentication, then Emails, then SMTP Settings. Turn on custom SMTP and enter these values.",
|
|
369
|
+
"host": "Host",
|
|
370
|
+
"port": "Port",
|
|
371
|
+
"username": "Username",
|
|
372
|
+
"password": "Password",
|
|
373
|
+
"passwordValue": "Your Resend API key",
|
|
374
|
+
"copyValue": "Copy {label}",
|
|
375
|
+
"dashboardTitle": "Supabase dashboard",
|
|
376
|
+
"dashboardSmtp": "SMTP settings",
|
|
377
|
+
"dashboardTemplates": "Email templates",
|
|
378
|
+
"dashboardRateLimits": "Rate limits",
|
|
379
|
+
"dashboardHome": "Open your project",
|
|
380
|
+
"dashboardNote": "These links open pages of the Supabase dashboard. Supabase can move them, so they are best effort. If a link fails, open your project and look under Authentication.",
|
|
381
|
+
"noProject": "Pluto could not read the project ref from your Supabase URL. Open the Supabase dashboard by hand.",
|
|
382
|
+
"rateLimitHint": "After you set custom SMTP, open Authentication, then Rate Limits, and raise the number of emails each hour. The default is low."
|
|
383
|
+
}
|
|
191
384
|
}
|
|
192
385
|
}
|
|
193
386
|
}
|
package/i18n/locales/pt-BR.json
CHANGED
|
@@ -87,6 +87,14 @@
|
|
|
87
87
|
"verificationSent": "Enviamos um link de verificação para o seu e-mail. Verifique sua caixa de entrada e siga as instruções para verificar sua conta.",
|
|
88
88
|
"closePage": "Você pode fechar esta página. Você entrará automaticamente assim que verificar seu e-mail.",
|
|
89
89
|
"checkSpam": "Se você não encontrar o e-mail, verifique sua pasta de spam."
|
|
90
|
+
},
|
|
91
|
+
"invite": {
|
|
92
|
+
"pageTitle": "Aceitar convite",
|
|
93
|
+
"title": "Boas-vindas!",
|
|
94
|
+
"description": "Escolha uma senha para concluir sua conta.",
|
|
95
|
+
"submit": "Salvar senha e continuar",
|
|
96
|
+
"expired": "Este link de convite expirou ou já foi usado. Peça a um administrador para enviar um novo convite.",
|
|
97
|
+
"invalid": "Este link de convite não é válido. Abra o link do e-mail de convite de novo, ou peça um novo convite a um administrador."
|
|
90
98
|
}
|
|
91
99
|
},
|
|
92
100
|
"settings": {
|
|
@@ -102,7 +110,8 @@
|
|
|
102
110
|
},
|
|
103
111
|
"capabilities": {
|
|
104
112
|
"settingsManage": "Gerenciar as configurações do site",
|
|
105
|
-
"usersRead": "Ver todas as contas de usuário"
|
|
113
|
+
"usersRead": "Ver todas as contas de usuário",
|
|
114
|
+
"group": "Supabase"
|
|
106
115
|
},
|
|
107
116
|
"envWarning": {
|
|
108
117
|
"title": "A string de conexão não foi salva automaticamente",
|
|
@@ -188,6 +197,190 @@
|
|
|
188
197
|
"loadFailed": "Não foi possível carregar o status das migrações",
|
|
189
198
|
"loadFailedDescription": "Este não é o caso de reinício do servidor de desenvolvimento descrito acima. Algo diferente deu errado ao ler o status das migrações. Verifique o console do servidor e tente novamente.",
|
|
190
199
|
"retry": "Tentar novamente"
|
|
200
|
+
},
|
|
201
|
+
"access": {
|
|
202
|
+
"users": {
|
|
203
|
+
"title": "Usuários",
|
|
204
|
+
"description": "As pessoas que podem usar o admin. Os clientes não aparecem aqui.",
|
|
205
|
+
"invite": "Convidar usuário",
|
|
206
|
+
"you": "Você",
|
|
207
|
+
"administrator": "Administrador",
|
|
208
|
+
"neverSignedIn": "Nunca",
|
|
209
|
+
"actionsLabel": "Ações para {name}",
|
|
210
|
+
"columns": {
|
|
211
|
+
"user": "Usuário",
|
|
212
|
+
"roles": "Papéis",
|
|
213
|
+
"status": "Status",
|
|
214
|
+
"lastSignIn": "Último acesso"
|
|
215
|
+
},
|
|
216
|
+
"status": {
|
|
217
|
+
"active": "Ativo",
|
|
218
|
+
"invited": "Convidado",
|
|
219
|
+
"invitedOn": "Convidado em {date}"
|
|
220
|
+
},
|
|
221
|
+
"menu": {
|
|
222
|
+
"editRoles": "Editar papéis",
|
|
223
|
+
"resend": "Reenviar convite",
|
|
224
|
+
"copyLink": "Copiar link do convite",
|
|
225
|
+
"revoke": "Revogar acesso"
|
|
226
|
+
},
|
|
227
|
+
"empty": {
|
|
228
|
+
"title": "Nenhum usuário ainda",
|
|
229
|
+
"description": "Convide a primeira pessoa para ajudar a gerenciar este site."
|
|
230
|
+
},
|
|
231
|
+
"error": {
|
|
232
|
+
"title": "Não foi possível carregar os usuários"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"invite": {
|
|
236
|
+
"title": "Convidar usuário",
|
|
237
|
+
"description": "A pessoa recebe um e-mail com um link para escolher uma senha. Os papéis valem quando ela entrar.",
|
|
238
|
+
"email": "E-mail",
|
|
239
|
+
"emailPlaceholder": "nome@exemplo.com",
|
|
240
|
+
"name": "Nome (opcional)",
|
|
241
|
+
"namePlaceholder": "ex.: Ada Lovelace",
|
|
242
|
+
"roles": "Papéis",
|
|
243
|
+
"rolesRequired": "Escolha pelo menos um papel.",
|
|
244
|
+
"locale": "Idioma do e-mail",
|
|
245
|
+
"localeHint": "O idioma do e-mail de convite.",
|
|
246
|
+
"submit": "Enviar convite",
|
|
247
|
+
"mailProblem": "O e-mail de convite não foi enviado",
|
|
248
|
+
"copyLink": "Copiar link do convite",
|
|
249
|
+
"setupEmail": "Configurar e-mail",
|
|
250
|
+
"needsKey": "Para copiar o link do convite, defina NUXT_SUPABASE_SECRET_KEY no servidor.",
|
|
251
|
+
"linkTitle": "O convite está pronto",
|
|
252
|
+
"linkDescription": "O usuário foi adicionado. Envie este link a ele por conta própria.",
|
|
253
|
+
"done": "Concluir"
|
|
254
|
+
},
|
|
255
|
+
"link": {
|
|
256
|
+
"label": "Link do convite",
|
|
257
|
+
"note": "Este link faz o login da pessoa uma vez e depois expira. Envie somente para ela.",
|
|
258
|
+
"copy": "Copiar link",
|
|
259
|
+
"copied": "Link copiado",
|
|
260
|
+
"title": "Link do convite"
|
|
261
|
+
},
|
|
262
|
+
"editRoles": {
|
|
263
|
+
"title": "Editar papéis",
|
|
264
|
+
"description": "Escolha os papéis de {name}.",
|
|
265
|
+
"save": "Salvar papéis",
|
|
266
|
+
"emptyHint": "Escolha pelo menos um papel. Para remover todo o acesso, use Revogar acesso.",
|
|
267
|
+
"selfAdmin": "Você está removendo seu próprio papel de administrador. Você perderá o acesso a Usuários e Papéis."
|
|
268
|
+
},
|
|
269
|
+
"revoke": {
|
|
270
|
+
"title": "Revogar acesso",
|
|
271
|
+
"body": "{name} perderá todos os papéis e não poderá usar o admin. A conta continua. Só o acesso ao admin é removido.",
|
|
272
|
+
"confirm": "Revogar acesso",
|
|
273
|
+
"self": "Esta é a sua própria conta. Você perderá o acesso ao admin."
|
|
274
|
+
},
|
|
275
|
+
"roles": {
|
|
276
|
+
"title": "Papéis",
|
|
277
|
+
"description": "Um papel é um conjunto de permissões. Dê papéis aos usuários para controlar o que eles podem fazer.",
|
|
278
|
+
"create": "Novo papel",
|
|
279
|
+
"builtin": "Nativo",
|
|
280
|
+
"fullAccess": "Acesso total",
|
|
281
|
+
"permissionCount": "{count} permissão | {count} permissões",
|
|
282
|
+
"userCount": "{count} usuário | {count} usuários",
|
|
283
|
+
"columns": {
|
|
284
|
+
"role": "Papel",
|
|
285
|
+
"permissions": "Permissões",
|
|
286
|
+
"users": "Usuários"
|
|
287
|
+
},
|
|
288
|
+
"menu": {
|
|
289
|
+
"edit": "Editar",
|
|
290
|
+
"view": "Ver",
|
|
291
|
+
"delete": "Excluir"
|
|
292
|
+
},
|
|
293
|
+
"error": {
|
|
294
|
+
"title": "Não foi possível carregar os papéis"
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"role": {
|
|
298
|
+
"titleCreate": "Novo papel",
|
|
299
|
+
"titleEdit": "Editar papel",
|
|
300
|
+
"titleView": "Papel",
|
|
301
|
+
"description": "Escolha o que este papel pode fazer.",
|
|
302
|
+
"label": "Nome",
|
|
303
|
+
"labelPlaceholder": "ex.: Editor",
|
|
304
|
+
"key": "Chave",
|
|
305
|
+
"keyHint": "Use a-z, 0-9 e sublinhado. Você não pode mudar a chave depois.",
|
|
306
|
+
"keyInvalid": "A chave só pode usar a-z, 0-9 e sublinhado.",
|
|
307
|
+
"about": "Descrição",
|
|
308
|
+
"aboutPlaceholder": "Para que serve este papel?",
|
|
309
|
+
"capabilities": "Permissões",
|
|
310
|
+
"capabilitiesRequired": "Escolha pelo menos uma permissão.",
|
|
311
|
+
"fullAccessNote": "Este papel tem acesso total. Ele não pode ser alterado.",
|
|
312
|
+
"save": "Salvar papel"
|
|
313
|
+
},
|
|
314
|
+
"deleteRole": {
|
|
315
|
+
"title": "Excluir papel",
|
|
316
|
+
"body": "Excluir o papel {name}? Isso não pode ser desfeito.",
|
|
317
|
+
"usersLose": "Nenhum usuário tem este papel. | 1 usuário perde este papel. | {count} usuários perdem este papel.",
|
|
318
|
+
"confirm": "Excluir papel"
|
|
319
|
+
},
|
|
320
|
+
"toasts": {
|
|
321
|
+
"invited": "Convite enviado para {email}.",
|
|
322
|
+
"granted": "{email} já tem uma conta. Os papéis foram concedidos.",
|
|
323
|
+
"noLinkConfirmed": "Esta conta já está confirmada. Ela entra com a própria senha, então não precisa de link.",
|
|
324
|
+
"emailFailed": "{email} foi adicionado, mas o e-mail não foi enviado. Copie o link do convite e envie por conta própria.",
|
|
325
|
+
"resent": "Convite reenviado para {email}.",
|
|
326
|
+
"resentNoEmail": "O e-mail não foi enviado. Copie o link do convite e envie por conta própria.",
|
|
327
|
+
"rolesSaved": "Papéis salvos.",
|
|
328
|
+
"revoked": "Acesso revogado.",
|
|
329
|
+
"roleCreated": "Papel criado.",
|
|
330
|
+
"roleSaved": "Papel salvo.",
|
|
331
|
+
"roleDeleted": "Papel excluído.",
|
|
332
|
+
"failed": "Isso não funcionou"
|
|
333
|
+
},
|
|
334
|
+
"errors": {
|
|
335
|
+
"forbidden": "Somente um administrador pode fazer isso.",
|
|
336
|
+
"lastAdmin": "Este é o último administrador. Torne outro usuário administrador primeiro.",
|
|
337
|
+
"roleExists": "Já existe um papel com esta chave.",
|
|
338
|
+
"builtinRole": "Um papel nativo não pode ser alterado nem excluído.",
|
|
339
|
+
"wildcard": "Um novo papel não pode ter a permissão curinga.",
|
|
340
|
+
"emptyRole": "Um papel precisa de pelo menos uma permissão.",
|
|
341
|
+
"unknownRole": "Um dos papéis não existe.",
|
|
342
|
+
"invalidKey": "A chave do papel só pode usar a-z, 0-9 e sublinhado.",
|
|
343
|
+
"invalidLabel": "O papel precisa de um nome.",
|
|
344
|
+
"invalidCapability": "Uma das permissões não é válida.",
|
|
345
|
+
"userNotFound": "Este usuário não existe.",
|
|
346
|
+
"roleNotFound": "Este papel não existe.",
|
|
347
|
+
"invalidUserId": "O id do usuário não é válido.",
|
|
348
|
+
"signupDisabled": "O cadastro público está desligado. Defina NUXT_SUPABASE_SECRET_KEY no servidor para enviar convites.",
|
|
349
|
+
"emailNotAuthorized": "O e-mail padrão do Supabase só envia para membros da sua organização no Supabase. Configure um SMTP próprio no Supabase para convidar outros endereços.",
|
|
350
|
+
"emailInvalid": "O Supabase não aceita este endereço de e-mail. Use um endereço real: o Supabase recusa domínios de exemplo e de teste.",
|
|
351
|
+
"inviteFailed": "Não foi possível enviar o e-mail de convite.",
|
|
352
|
+
"rateLimited": "Convites demais. Aguarde alguns minutos e tente de novo.",
|
|
353
|
+
"emailRateLimited": "O Supabase enviou e-mails demais. Aguarde um minuto para o mesmo endereço, ou até uma hora com o e-mail padrão. Um SMTP próprio no Supabase aumenta esse limite.",
|
|
354
|
+
"alreadyAccepted": "Este usuário já aceitou o convite.",
|
|
355
|
+
"migrationPending": "Aplique as migrações pendentes do banco de dados para gerenciar usuários e papéis.",
|
|
356
|
+
"serviceKeyRequired": "Defina NUXT_SUPABASE_SECRET_KEY no servidor para gerar links de convite."
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"mail": {
|
|
360
|
+
"authEmails": {
|
|
361
|
+
"title": "E-mails de login do Supabase",
|
|
362
|
+
"description": "E-mails de cadastro, redefinição de senha e link mágico.",
|
|
363
|
+
"intro": "O Supabase envia esses e-mails, não o Pluto. Sem SMTP próprio, o Supabase os envia só para os membros da sua equipe no Supabase, e apenas alguns por hora. Os convites da página Usuários usam o e-mail do Pluto quando a chave de serviço está definida.",
|
|
364
|
+
"resendTitle": "Conectar o Resend ao Supabase",
|
|
365
|
+
"resendBody": "O Resend pode preencher as configurações de SMTP do seu projeto Supabase para você. Você entra nos dois serviços. O Pluto não guarda chave.",
|
|
366
|
+
"resendLink": "Abrir a integração do Resend",
|
|
367
|
+
"manualTitle": "Ou informe os valores de SMTP manualmente",
|
|
368
|
+
"manualBody": "No Supabase, abra Authentication, depois Emails, depois SMTP Settings. Ative o SMTP próprio e informe estes valores.",
|
|
369
|
+
"host": "Servidor",
|
|
370
|
+
"port": "Porta",
|
|
371
|
+
"username": "Usuário",
|
|
372
|
+
"password": "Senha",
|
|
373
|
+
"passwordValue": "Sua chave de API do Resend",
|
|
374
|
+
"copyValue": "Copiar {label}",
|
|
375
|
+
"dashboardTitle": "Painel do Supabase",
|
|
376
|
+
"dashboardSmtp": "Configurações de SMTP",
|
|
377
|
+
"dashboardTemplates": "Modelos de e-mail",
|
|
378
|
+
"dashboardRateLimits": "Limites de envio",
|
|
379
|
+
"dashboardHome": "Abrir seu projeto",
|
|
380
|
+
"dashboardNote": "Estes links abrem páginas do painel do Supabase. O Supabase pode mudá-las, então são feitos no melhor esforço. Se um link falhar, abra seu projeto e procure em Authentication.",
|
|
381
|
+
"noProject": "O Pluto não conseguiu ler a referência do projeto na sua URL do Supabase. Abra o painel do Supabase manualmente.",
|
|
382
|
+
"rateLimitHint": "Depois de configurar o SMTP próprio, abra Authentication, depois Rate Limits, e aumente o número de e-mails por hora. O padrão é baixo."
|
|
383
|
+
}
|
|
191
384
|
}
|
|
192
385
|
}
|
|
193
386
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plutocms/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"trustedDependencies": [
|
|
6
6
|
"@parcel/watcher",
|
|
7
7
|
"@plutocms/pluto",
|
|
@@ -39,8 +39,9 @@
|
|
|
39
39
|
"@nuxt/eslint": "^1.17.0",
|
|
40
40
|
"@nuxt/ui": "^4.12.0",
|
|
41
41
|
"@nuxtjs/supabase": "^2.1.0",
|
|
42
|
-
"@plutocms/pluto": "^0.
|
|
42
|
+
"@plutocms/pluto": "^0.18.0",
|
|
43
43
|
"@plutocms/utils": "^0.2.1",
|
|
44
|
+
"@supabase/supabase-js": "^2.117.3",
|
|
44
45
|
"postgres": "^3.4.9",
|
|
45
46
|
"supabase": "^2.121.0",
|
|
46
47
|
"zod": "^4.6.7"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { plutoMailReady } from '../../utils/access-mail'
|
|
2
|
+
import { requireAdminRole } from '../../utils/admin-role-guard'
|
|
3
|
+
import { optionalServiceClient } from '../../utils/service-client'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What the invite UI can offer, for an admin: whether the service role key is
|
|
7
|
+
* set (copy invite link, invites through Pluto mail) and whether Pluto mail can
|
|
8
|
+
* send. It holds no secret.
|
|
9
|
+
*/
|
|
10
|
+
export default defineEventHandler(async (event) => {
|
|
11
|
+
await requireAdminRole(event)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
serviceKey: optionalServiceClient(event) !== null,
|
|
15
|
+
mail: { configured: await plutoMailReady() },
|
|
16
|
+
}
|
|
17
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { serverSupabaseClient } from '#supabase/server'
|
|
2
|
+
import { mapAccessError } from '../../../utils/access-errors'
|
|
3
|
+
import { requireAdminRole } from '../../../utils/admin-role-guard'
|
|
4
|
+
|
|
5
|
+
/** Deletes a role. It answers with the number of users who lost the role. */
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
await requireAdminRole(event)
|
|
8
|
+
|
|
9
|
+
const key = event.context.params?.key as string
|
|
10
|
+
const client = await serverSupabaseClient<Database>(event)
|
|
11
|
+
const { data, error } = await client.rpc('delete_role', { p_key: key })
|
|
12
|
+
|
|
13
|
+
if (error) {
|
|
14
|
+
throw mapAccessError(error)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { key, removedFromUsers: data ?? 0 }
|
|
18
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { serverSupabaseClient } from '#supabase/server'
|
|
2
|
+
import { mapAccessError } from '../../../utils/access-errors'
|
|
3
|
+
import { roleBodySchema } from '../../../utils/access-invite'
|
|
4
|
+
import { requireAdminRole } from '../../../utils/admin-role-guard'
|
|
5
|
+
import { parseBody } from '../../../utils/validate'
|
|
6
|
+
|
|
7
|
+
/** Edits a role and replaces its capabilities. The `admin` role is refused. */
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
await requireAdminRole(event)
|
|
10
|
+
|
|
11
|
+
const key = event.context.params?.key as string
|
|
12
|
+
const body = await parseBody(event, roleBodySchema)
|
|
13
|
+
const client = await serverSupabaseClient<Database>(event)
|
|
14
|
+
const { error } = await client.rpc('update_role', {
|
|
15
|
+
p_key: key,
|
|
16
|
+
p_label: body.label,
|
|
17
|
+
p_description: body.description ?? '',
|
|
18
|
+
p_capabilities: body.capabilities,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (error) {
|
|
22
|
+
throw mapAccessError(error)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { key }
|
|
26
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { serverSupabaseClient } from '#supabase/server'
|
|
2
|
+
import { mapAccessError } from '../../utils/access-errors'
|
|
3
|
+
import { requireCapability } from '../../utils/capability-guard'
|
|
4
|
+
|
|
5
|
+
/** The roles, with their capabilities and the number of users. */
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
await requireCapability(event, 'users:read')
|
|
8
|
+
|
|
9
|
+
const client = await serverSupabaseClient<Database>(event)
|
|
10
|
+
const { data, error } = await client.rpc('list_roles')
|
|
11
|
+
|
|
12
|
+
if (error) {
|
|
13
|
+
throw mapAccessError(error)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return { roles: data ?? [] }
|
|
17
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { serverSupabaseClient } from '#supabase/server'
|
|
2
|
+
import { mapAccessError } from '../../utils/access-errors'
|
|
3
|
+
import { createRoleBodySchema } from '../../utils/access-invite'
|
|
4
|
+
import { requireAdminRole } from '../../utils/admin-role-guard'
|
|
5
|
+
import { parseBody } from '../../utils/validate'
|
|
6
|
+
|
|
7
|
+
/** Creates a role. A new role cannot hold the wildcard. */
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
await requireAdminRole(event)
|
|
10
|
+
|
|
11
|
+
const body = await parseBody(event, createRoleBodySchema)
|
|
12
|
+
const client = await serverSupabaseClient<Database>(event)
|
|
13
|
+
const { error } = await client.rpc('create_role', {
|
|
14
|
+
p_key: body.key,
|
|
15
|
+
p_label: body.label,
|
|
16
|
+
p_description: body.description ?? '',
|
|
17
|
+
p_capabilities: body.capabilities,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (error) {
|
|
21
|
+
throw mapAccessError(error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { key: body.key }
|
|
25
|
+
})
|