@dudousxd/adonis-authkit-server 0.6.0 → 0.7.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/build/host/views/account/security.edge +14 -1
- package/build/src/define_config.d.ts +29 -0
- package/build/src/define_config.js +10 -0
- package/build/src/host/avatar_storage.d.ts +58 -0
- package/build/src/host/avatar_storage.js +125 -0
- package/build/src/host/controllers/account_security_controller.js +33 -1
- package/build/src/host/i18n.d.ts +8 -0
- package/build/src/host/i18n.js +8 -0
- package/package.json +6 -1
|
@@ -33,13 +33,26 @@
|
|
|
33
33
|
<div class="mb-6 rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
|
|
34
34
|
<h2 class="mb-1 text-sm font-semibold text-gray-900">{{ t('account.profile.section') }}</h2>
|
|
35
35
|
<p class="mb-4 text-xs text-gray-500">{{ t('account.profile.intro') }}</p>
|
|
36
|
-
<form method="POST" action="/account/security/profile" class="space-y-4">
|
|
36
|
+
<form method="POST" action="/account/security/profile" enctype="multipart/form-data" class="space-y-4">
|
|
37
37
|
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
|
|
38
|
+
@if(avatarUrl)
|
|
39
|
+
<div>
|
|
40
|
+
<img src="{{ avatarUrl }}" alt="avatar" class="h-16 w-16 rounded-full object-cover ring-1 ring-black/5" />
|
|
41
|
+
</div>
|
|
42
|
+
@end
|
|
38
43
|
<div>
|
|
39
44
|
<label class="mb-1 block text-sm font-medium text-gray-700">{{ t('account.profile.name_label') }}</label>
|
|
40
45
|
<input name="name" type="text" value="{{ name }}" maxlength="255"
|
|
41
46
|
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
42
47
|
</div>
|
|
48
|
+
@if(avatarUploadSupported)
|
|
49
|
+
<div>
|
|
50
|
+
<label class="mb-1 block text-sm font-medium text-gray-700">{{ t('account.profile.avatar_upload_label') }}</label>
|
|
51
|
+
<input name="avatar" type="file" accept="image/jpeg,image/png,image/webp"
|
|
52
|
+
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-gray-900" />
|
|
53
|
+
<p class="mt-1 text-xs text-gray-500">{{ t('account.profile.avatar_upload_hint') }}</p>
|
|
54
|
+
</div>
|
|
55
|
+
@end
|
|
43
56
|
<div>
|
|
44
57
|
<label class="mb-1 block text-sm font-medium text-gray-700">{{ t('account.profile.avatar_label') }}</label>
|
|
45
58
|
<input name="avatarUrl" type="url" value="{{ avatarUrl }}"
|
|
@@ -161,6 +161,31 @@ export interface ResolvedDeviceFlowConfig {
|
|
|
161
161
|
enabled: boolean;
|
|
162
162
|
}
|
|
163
163
|
export declare function resolveDeviceFlow(input?: DeviceFlowConfigInput): ResolvedDeviceFlowConfig;
|
|
164
|
+
/**
|
|
165
|
+
* Uploads — usa o `@adonisjs/drive` JÁ configurado no app (mesmo princípio do
|
|
166
|
+
* mailer/limiter: a infra do host por padrão, sobreponível aqui). Hoje cobre o
|
|
167
|
+
* upload de avatar no console de conta. Se o drive estiver ausente, a feature
|
|
168
|
+
* degrada para o input de URL.
|
|
169
|
+
*/
|
|
170
|
+
export interface UploadsConfigInput {
|
|
171
|
+
avatars?: {
|
|
172
|
+
/** Disk do `@adonisjs/drive` a usar. Default: o disk DEFAULT do app. */
|
|
173
|
+
disk?: string;
|
|
174
|
+
/** Diretório/prefixo das chaves. Default: 'authkit/avatars'. */
|
|
175
|
+
directory?: string;
|
|
176
|
+
/** Tamanho máximo em MB. Default: 5. */
|
|
177
|
+
maxSizeMb?: number;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export interface ResolvedUploadsConfig {
|
|
181
|
+
avatars: {
|
|
182
|
+
/** Disk explícito; `undefined` = disk DEFAULT do app. */
|
|
183
|
+
disk?: string;
|
|
184
|
+
directory: string;
|
|
185
|
+
maxSizeMb: number;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export declare function resolveUploads(input?: UploadsConfigInput): ResolvedUploadsConfig;
|
|
164
189
|
/**
|
|
165
190
|
* DPoP — Demonstrating Proof of Possession (RFC 9449). Quando habilitado, o
|
|
166
191
|
* oidc-provider aceita DPoP proofs e emite tokens sender-constrained
|
|
@@ -337,6 +362,8 @@ export interface AuthServerConfigInput {
|
|
|
337
362
|
dynamicRegistration?: DynamicRegistrationConfigInput;
|
|
338
363
|
/** Device Authorization Grant (RFC 8628). Default: desligado. */
|
|
339
364
|
deviceFlow?: DeviceFlowConfigInput;
|
|
365
|
+
/** Uploads (avatar) via o `@adonisjs/drive` do app. Default: drive default, 5MB. */
|
|
366
|
+
uploads?: UploadsConfigInput;
|
|
340
367
|
/** DPoP — sender-constrained tokens (RFC 9449). Default: desligado. */
|
|
341
368
|
dpop?: DpopConfigInput;
|
|
342
369
|
/** Pushed Authorization Requests (RFC 9126). Default: desligado. */
|
|
@@ -401,6 +428,8 @@ export interface ResolvedServerConfig {
|
|
|
401
428
|
dynamicRegistration: ResolvedDynamicRegistrationConfig;
|
|
402
429
|
/** Device Authorization Grant resolvido (default desligado). */
|
|
403
430
|
deviceFlow: ResolvedDeviceFlowConfig;
|
|
431
|
+
/** Uploads resolvido (avatar via drive do app; sempre presente). */
|
|
432
|
+
uploads: ResolvedUploadsConfig;
|
|
404
433
|
/** DPoP resolvido (default desligado). */
|
|
405
434
|
dpop: ResolvedDpopConfig;
|
|
406
435
|
/** PAR resolvido (default desligado). */
|
|
@@ -56,6 +56,15 @@ export function resolveDynamicRegistration(input) {
|
|
|
56
56
|
export function resolveDeviceFlow(input) {
|
|
57
57
|
return { enabled: input?.enabled ?? false };
|
|
58
58
|
}
|
|
59
|
+
export function resolveUploads(input) {
|
|
60
|
+
return {
|
|
61
|
+
avatars: {
|
|
62
|
+
disk: input?.avatars?.disk,
|
|
63
|
+
directory: input?.avatars?.directory ?? 'authkit/avatars',
|
|
64
|
+
maxSizeMb: input?.avatars?.maxSizeMb ?? 5,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
59
68
|
export function resolveDpop(input) {
|
|
60
69
|
return { enabled: input?.enabled ?? false };
|
|
61
70
|
}
|
|
@@ -173,6 +182,7 @@ export function defineConfig(config) {
|
|
|
173
182
|
webauthn: resolveWebauthn(config.issuer, config.mfaIssuer ?? 'AuthKit', config.webauthn),
|
|
174
183
|
dynamicRegistration: resolveDynamicRegistration(config.dynamicRegistration),
|
|
175
184
|
deviceFlow: resolveDeviceFlow(config.deviceFlow),
|
|
185
|
+
uploads: resolveUploads(config.uploads),
|
|
176
186
|
dpop: resolveDpop(config.dpop),
|
|
177
187
|
par: resolvePar(config.par),
|
|
178
188
|
stepUp: resolveStepUp(config.stepUp),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ResolvedUploadsConfig } from '../define_config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Upload de avatar do host-kit usando o `@adonisjs/drive` JÁ configurado no app.
|
|
5
|
+
* Mesmo princípio do default_mailer/rate_limit: por padrão usamos a infra do
|
|
6
|
+
* host (o disk DEFAULT do drive do app), sem o dev precisar escrever nada; tudo
|
|
7
|
+
* é sobreponível via `config/authkit.ts` (`uploads.avatars`).
|
|
8
|
+
*
|
|
9
|
+
* Best-effort / fail-safe: se `@adonisjs/drive` não estiver instalado/configurado,
|
|
10
|
+
* {@link storeAvatar} retorna `null` e a feature degrada para o input de URL.
|
|
11
|
+
* Nunca lança na request por causa de drive ausente.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Service do `@adonisjs/drive` resolvido de forma preguiçosa. Tipado como `any` de
|
|
15
|
+
* propósito: a lib NÃO depende do drive em tempo de compilação (peer/opt-in).
|
|
16
|
+
*/
|
|
17
|
+
type DriveService = any;
|
|
18
|
+
/**
|
|
19
|
+
* Permite reapontar/limpar o loader do drive (usado em testes).
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export declare function __setDriveLoaderForTests(fn: (() => Promise<DriveService | null>) | undefined): void;
|
|
23
|
+
/** Erro de validação do upload (mensagem já localizada no controller). */
|
|
24
|
+
export declare class AvatarUploadError extends Error {
|
|
25
|
+
reason: 'extname' | 'size';
|
|
26
|
+
constructor(reason: 'extname' | 'size', message: string);
|
|
27
|
+
}
|
|
28
|
+
/** File de multipart mínimo que precisamos do `request.file('avatar')`. */
|
|
29
|
+
export interface UploadedAvatar {
|
|
30
|
+
extname?: string | null;
|
|
31
|
+
size?: number;
|
|
32
|
+
/** Caminho temporário (drive lê daqui para stream). */
|
|
33
|
+
tmpPath?: string | null;
|
|
34
|
+
/** Move o arquivo para um disk do drive (API v3+). */
|
|
35
|
+
moveToDisk?: (key: string, options?: {
|
|
36
|
+
disk?: string;
|
|
37
|
+
}) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Indica se o drive do app está disponível (para a view decidir mostrar o input
|
|
41
|
+
* de arquivo). Best-effort: nunca lança.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isDriveAvailable(): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Armazena o avatar no drive do host e retorna a URL pública.
|
|
46
|
+
*
|
|
47
|
+
* - Valida extensão (jpg/jpeg/png/webp) e tamanho (≤ maxSizeMb) — lança
|
|
48
|
+
* {@link AvatarUploadError} se inválido (o controller traduz/flasha).
|
|
49
|
+
* - Usa o disk configurado em `uploads.avatars.disk` ou o disk DEFAULT do app.
|
|
50
|
+
* - Se o drive estiver ausente/não-configurado → retorna `null` (degrada para URL).
|
|
51
|
+
*
|
|
52
|
+
* Nunca lança por causa de drive ausente; só lança em validação.
|
|
53
|
+
*/
|
|
54
|
+
export declare function storeAvatar(_ctx: HttpContext, cfg: ResolvedUploadsConfig, file: UploadedAvatar, accountId: string, messages: {
|
|
55
|
+
extname: string;
|
|
56
|
+
size: string;
|
|
57
|
+
}): Promise<string | null>;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
let driveServicePromise;
|
|
10
|
+
/**
|
|
11
|
+
* Importa o service de drive do HOST de forma preguiçosa e fail-safe.
|
|
12
|
+
* Se `@adonisjs/drive` não estiver instalado, resolve `null`.
|
|
13
|
+
*/
|
|
14
|
+
async function loadDrive() {
|
|
15
|
+
if (!driveServicePromise) {
|
|
16
|
+
// Indireção via variável: o `@adonisjs/drive` é peer/opcional e pode não estar
|
|
17
|
+
// instalado na lib, então o specifier não é resolvido em build-time.
|
|
18
|
+
const specifier = '@adonisjs/drive/services/main';
|
|
19
|
+
driveServicePromise = import(__rewriteRelativeImportExtension(specifier))
|
|
20
|
+
.then((mod) => mod.default ?? null)
|
|
21
|
+
.catch(() => null);
|
|
22
|
+
}
|
|
23
|
+
return driveServicePromise;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Permite reapontar/limpar o loader do drive (usado em testes).
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function __setDriveLoaderForTests(fn) {
|
|
30
|
+
if (fn) {
|
|
31
|
+
driveServicePromise = fn();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
driveServicePromise = undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Extensões aceitas para o avatar (imagem raster comum). */
|
|
38
|
+
const ALLOWED_EXTNAMES = ['jpg', 'jpeg', 'png', 'webp'];
|
|
39
|
+
/** Erro de validação do upload (mensagem já localizada no controller). */
|
|
40
|
+
export class AvatarUploadError extends Error {
|
|
41
|
+
reason;
|
|
42
|
+
constructor(reason, message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.reason = reason;
|
|
45
|
+
this.name = 'AvatarUploadError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Indica se o drive do app está disponível (para a view decidir mostrar o input
|
|
50
|
+
* de arquivo). Best-effort: nunca lança.
|
|
51
|
+
*/
|
|
52
|
+
export async function isDriveAvailable() {
|
|
53
|
+
return (await loadDrive()) !== null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a extensão validada do arquivo enviado.
|
|
57
|
+
* Lança {@link AvatarUploadError} se ext/size forem inválidos.
|
|
58
|
+
*/
|
|
59
|
+
function validate(file, cfg, messages) {
|
|
60
|
+
const ext = (file.extname ?? '').toLowerCase().replace(/^\./, '');
|
|
61
|
+
if (!ALLOWED_EXTNAMES.includes(ext)) {
|
|
62
|
+
throw new AvatarUploadError('extname', messages.extname);
|
|
63
|
+
}
|
|
64
|
+
const maxBytes = cfg.avatars.maxSizeMb * 1024 * 1024;
|
|
65
|
+
if (typeof file.size === 'number' && file.size > maxBytes) {
|
|
66
|
+
throw new AvatarUploadError('size', messages.size);
|
|
67
|
+
}
|
|
68
|
+
return ext;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gera uma chave única para o avatar dentro do diretório configurado.
|
|
72
|
+
* `${directory}/${accountId}-${random}.${ext}`
|
|
73
|
+
*/
|
|
74
|
+
function buildKey(cfg, accountId, ext) {
|
|
75
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
76
|
+
return `${cfg.avatars.directory}/${accountId}-${random}.${ext}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Armazena o avatar no drive do host e retorna a URL pública.
|
|
80
|
+
*
|
|
81
|
+
* - Valida extensão (jpg/jpeg/png/webp) e tamanho (≤ maxSizeMb) — lança
|
|
82
|
+
* {@link AvatarUploadError} se inválido (o controller traduz/flasha).
|
|
83
|
+
* - Usa o disk configurado em `uploads.avatars.disk` ou o disk DEFAULT do app.
|
|
84
|
+
* - Se o drive estiver ausente/não-configurado → retorna `null` (degrada para URL).
|
|
85
|
+
*
|
|
86
|
+
* Nunca lança por causa de drive ausente; só lança em validação.
|
|
87
|
+
*/
|
|
88
|
+
export async function storeAvatar(_ctx, cfg, file, accountId, messages) {
|
|
89
|
+
const drive = await loadDrive();
|
|
90
|
+
if (!drive)
|
|
91
|
+
return null;
|
|
92
|
+
const ext = validate(file, cfg, messages);
|
|
93
|
+
// Resolve o disk: o configurado, ou o DEFAULT do drive do app.
|
|
94
|
+
let disk;
|
|
95
|
+
try {
|
|
96
|
+
disk = cfg.avatars.disk ? drive.use(cfg.avatars.disk) : drive.use();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// disk inválido/não-configurado — degrada para URL.
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!disk)
|
|
103
|
+
return null;
|
|
104
|
+
const key = buildKey(cfg, accountId || 'account', ext);
|
|
105
|
+
// API @adonisjs/drive v3+: o file de multipart move-se direto para o disk.
|
|
106
|
+
// `moveToDisk` lê do tmpPath e usa o disk informado (ou o default da config).
|
|
107
|
+
if (typeof file.moveToDisk === 'function') {
|
|
108
|
+
await file.moveToDisk(key, cfg.avatars.disk ? { disk: cfg.avatars.disk } : undefined);
|
|
109
|
+
}
|
|
110
|
+
else if (file.tmpPath) {
|
|
111
|
+
// Fallback: lê do tmpPath e escreve via putStream no disk resolvido.
|
|
112
|
+
const fs = await import('node:fs');
|
|
113
|
+
await disk.putStream(key, fs.createReadStream(file.tmpPath));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
return await disk.getUrl(key);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// disk sem getUrl público — retorna a key como referência relativa.
|
|
123
|
+
return key;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -3,6 +3,7 @@ import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
|
3
3
|
import { supportsAccountSecurity, supportsProfile } from '../../accounts/account_store.js';
|
|
4
4
|
import { changePasswordValidator, changeEmailValidator, updateProfileValidator } from '../validators.js';
|
|
5
5
|
import { sendEmailChangeConfirmationEmail } from '../default_mailer.js';
|
|
6
|
+
import { storeAvatar, isDriveAvailable, AvatarUploadError } from '../avatar_storage.js';
|
|
6
7
|
import { translate } from '../i18n.js';
|
|
7
8
|
import { TRUSTED_DEVICE_COOKIE } from '../trusted_device.js';
|
|
8
9
|
/**
|
|
@@ -23,6 +24,8 @@ export default class AccountSecurityController {
|
|
|
23
24
|
csrfToken: ctx.request.csrfToken,
|
|
24
25
|
supported: supportsAccountSecurity(cfg.accountStore),
|
|
25
26
|
profileSupported: supportsProfile(cfg.accountStore),
|
|
27
|
+
// Só mostramos o input de arquivo se o drive do app estiver disponível.
|
|
28
|
+
avatarUploadSupported: await isDriveAvailable(),
|
|
26
29
|
email: account?.email ?? '',
|
|
27
30
|
name: account?.name ?? '',
|
|
28
31
|
avatarUrl: account?.avatarUrl ?? '',
|
|
@@ -64,16 +67,45 @@ export default class AccountSecurityController {
|
|
|
64
67
|
return ctx.response.redirect('/account/security');
|
|
65
68
|
}
|
|
66
69
|
const { name, avatarUrl } = await ctx.request.validateUsing(updateProfileValidator);
|
|
70
|
+
// Upload de avatar via o drive do app (opt-in pela presença do arquivo).
|
|
71
|
+
// Se um arquivo for enviado e o drive estiver disponível, a URL resultante
|
|
72
|
+
// tem prioridade sobre o input de URL; senão caímos no avatarUrl (texto).
|
|
73
|
+
let resolvedAvatarUrl = avatarUrl ?? null;
|
|
74
|
+
let via = 'url';
|
|
75
|
+
const file = ctx.request.file('avatar', {
|
|
76
|
+
size: `${cfg.uploads.avatars.maxSizeMb}mb`,
|
|
77
|
+
extnames: ['jpg', 'jpeg', 'png', 'webp'],
|
|
78
|
+
});
|
|
79
|
+
if (file) {
|
|
80
|
+
try {
|
|
81
|
+
const uploadedUrl = await storeAvatar(ctx, cfg.uploads, file, userId, {
|
|
82
|
+
extname: translate(cfg.messages, 'account.profile.avatar_invalid_type'),
|
|
83
|
+
size: translate(cfg.messages, 'account.profile.avatar_too_large'),
|
|
84
|
+
});
|
|
85
|
+
if (uploadedUrl) {
|
|
86
|
+
resolvedAvatarUrl = uploadedUrl;
|
|
87
|
+
via = 'upload';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof AvatarUploadError) {
|
|
92
|
+
ctx.session.flash('securityError', error.message);
|
|
93
|
+
return ctx.response.redirect('/account/security');
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
67
98
|
// Campos ausentes no form viram string vazia (limpa o valor); enviamos null
|
|
68
99
|
// para limpar, ou o valor trimado.
|
|
69
100
|
await store.updateProfile(userId, {
|
|
70
101
|
name: name ?? null,
|
|
71
|
-
avatarUrl:
|
|
102
|
+
avatarUrl: resolvedAvatarUrl,
|
|
72
103
|
});
|
|
73
104
|
await cfg.audit?.record({
|
|
74
105
|
type: 'profile.updated',
|
|
75
106
|
accountId: userId,
|
|
76
107
|
ip: ctx.request.ip?.() ?? null,
|
|
108
|
+
metadata: { via },
|
|
77
109
|
});
|
|
78
110
|
ctx.session.flash('profileUpdated', translate(cfg.messages, 'account.profile.updated'));
|
|
79
111
|
return ctx.response.redirect('/account/security');
|
package/build/src/host/i18n.d.ts
CHANGED
|
@@ -133,6 +133,10 @@ export declare const DEFAULT_MESSAGES: {
|
|
|
133
133
|
'account.profile.intro': string;
|
|
134
134
|
'account.profile.name_label': string;
|
|
135
135
|
'account.profile.avatar_label': string;
|
|
136
|
+
'account.profile.avatar_upload_label': string;
|
|
137
|
+
'account.profile.avatar_upload_hint': string;
|
|
138
|
+
'account.profile.avatar_invalid_type': string;
|
|
139
|
+
'account.profile.avatar_too_large': string;
|
|
136
140
|
'account.profile.submit': string;
|
|
137
141
|
'account.profile.updated': string;
|
|
138
142
|
'account.profile.not_supported': string;
|
|
@@ -432,6 +436,10 @@ export declare const PT_BR_MESSAGES: {
|
|
|
432
436
|
'account.profile.intro': string;
|
|
433
437
|
'account.profile.name_label': string;
|
|
434
438
|
'account.profile.avatar_label': string;
|
|
439
|
+
'account.profile.avatar_upload_label': string;
|
|
440
|
+
'account.profile.avatar_upload_hint': string;
|
|
441
|
+
'account.profile.avatar_invalid_type': string;
|
|
442
|
+
'account.profile.avatar_too_large': string;
|
|
435
443
|
'account.profile.submit': string;
|
|
436
444
|
'account.profile.updated': string;
|
|
437
445
|
'account.profile.not_supported': string;
|
package/build/src/host/i18n.js
CHANGED
|
@@ -135,6 +135,10 @@ export const DEFAULT_MESSAGES = {
|
|
|
135
135
|
'account.profile.intro': 'Update your display name and avatar.',
|
|
136
136
|
'account.profile.name_label': 'Name',
|
|
137
137
|
'account.profile.avatar_label': 'Avatar URL',
|
|
138
|
+
'account.profile.avatar_upload_label': 'Upload avatar',
|
|
139
|
+
'account.profile.avatar_upload_hint': 'JPG, PNG or WebP, up to 5MB.',
|
|
140
|
+
'account.profile.avatar_invalid_type': 'Invalid image type. Use JPG, PNG or WebP.',
|
|
141
|
+
'account.profile.avatar_too_large': 'Image is too large.',
|
|
138
142
|
'account.profile.submit': 'Save profile',
|
|
139
143
|
'account.profile.updated': 'Profile updated successfully.',
|
|
140
144
|
'account.profile.not_supported': 'Profile editing is not available in this installation.',
|
|
@@ -463,6 +467,10 @@ export const PT_BR_MESSAGES = {
|
|
|
463
467
|
'account.profile.intro': 'Atualize seu nome de exibição e avatar.',
|
|
464
468
|
'account.profile.name_label': 'Nome',
|
|
465
469
|
'account.profile.avatar_label': 'URL do avatar',
|
|
470
|
+
'account.profile.avatar_upload_label': 'Enviar avatar',
|
|
471
|
+
'account.profile.avatar_upload_hint': 'JPG, PNG ou WebP, até 5MB.',
|
|
472
|
+
'account.profile.avatar_invalid_type': 'Tipo de imagem inválido. Use JPG, PNG ou WebP.',
|
|
473
|
+
'account.profile.avatar_too_large': 'A imagem é muito grande.',
|
|
466
474
|
'account.profile.submit': 'Salvar perfil',
|
|
467
475
|
'account.profile.updated': 'Perfil atualizado com sucesso.',
|
|
468
476
|
'account.profile.not_supported': 'A edição de perfil não está disponível nesta instalação.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/adonis-authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@adonisjs/ally": "6.3.0",
|
|
56
56
|
"@adonisjs/core": "7.3.3",
|
|
57
|
+
"@adonisjs/drive": "4.0.0",
|
|
57
58
|
"@adonisjs/lucid": "22.4.2",
|
|
58
59
|
"@adonisjs/session": "8.1.0",
|
|
59
60
|
"@adonisjs/shield": "9.0.0",
|
|
@@ -65,6 +66,9 @@
|
|
|
65
66
|
},
|
|
66
67
|
"@adonisjs/ally": {
|
|
67
68
|
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@adonisjs/drive": {
|
|
71
|
+
"optional": true
|
|
68
72
|
}
|
|
69
73
|
},
|
|
70
74
|
"dependencies": {
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
"devDependencies": {
|
|
82
86
|
"@adonisjs/ally": "6.3.0",
|
|
83
87
|
"@adonisjs/core": "7.3.3",
|
|
88
|
+
"@adonisjs/drive": "4.0.0",
|
|
84
89
|
"@adonisjs/lucid": "22.4.2",
|
|
85
90
|
"@adonisjs/session": "8.1.0",
|
|
86
91
|
"@adonisjs/shield": "9.0.0",
|