smoonb 1.0.8 → 1.0.9
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/CHANGELOG.md +17 -0
- package/README.md +5 -1
- package/README.pt-BR.md +5 -1
- package/package.json +1 -1
- package/src/commands/backup/steps/05-auth-settings.js +6 -2
- package/src/commands/restore/auth.js +65 -0
- package/src/commands/restore/index.js +4 -1
- package/src/commands/restore/management.js +28 -6
- package/src/commands/restore/runner.js +29 -6
- package/src/commands/restore/steps/05-auth-settings.js +11 -22
- package/src/i18n/locales/en.json +6 -0
- package/src/i18n/locales/pt-BR.json +6 -0
- package/src/utils/auth-secrets.js +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.0.9] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Treat SMTP passwords as write-only credentials. Never replay the protected SMTP value returned by the Management API from a legacy backup as the provider password.
|
|
12
|
+
- Ask for the real SMTP password with hidden input before restoring any selected components. Fail before remote mutations if it is missing. Keep the entered password in memory only.
|
|
13
|
+
- Verify readable Auth settings independently from password submission. Continue after the API accepts the supplied password, but report `applied_with_unverified_secrets` and require an authentication email test instead of claiming password or email-delivery verification.
|
|
14
|
+
- Omit the protected SMTP value from new Auth snapshots and explicitly record that the provider password is required during restore. Existing backups remain usable.
|
|
15
|
+
|
|
16
|
+
### Recovery from 1.0.8
|
|
17
|
+
|
|
18
|
+
- If restore stopped at `Configuration verification failed: smtp_pass`, inspect its `report.json`. When Database and Edge Functions are already verified, run restore with those two components disabled and select Auth, Storage, Database Settings and Realtime as needed. Supply the real provider SMTP password when prompted; no new backup or destination project is required for this specific failure.
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
|
|
22
|
+
- Cover legacy/new snapshots, hidden input, missing-password preflight, protected-value rejection, HTTP failures, readable-setting mismatches, partial verification reporting and continuation without reloading the database.
|
|
23
|
+
|
|
7
24
|
## [1.0.8] - 2026-10-10
|
|
8
25
|
|
|
9
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -313,7 +313,11 @@ Database loading uses one transaction, stops on errors, suspends triggers only o
|
|
|
313
313
|
|
|
314
314
|
Optional URL remapping changes only the project's Supabase hostname, including URLs inside JSON and text files. It does not rename files or replace isolated project IDs. Historical URLs may also change; choose preservation when needed. Signed URLs, custom domains, external integrations and application API keys need separate validation. Destination platform keys are not replaced.
|
|
315
315
|
|
|
316
|
-
Auth and Realtime are applied through the Management API and verified by reading them back.
|
|
316
|
+
Readable Auth and Realtime settings are applied through the Management API and verified by reading them back. The **SMTP password cannot be exported**: Supabase returns a protected value that cannot be used as the password in another project. Starting with 1.0.9, smoonb requests the real provider password with hidden input **before restoring any components**, including when using legacy backups. It stays in memory only and is sent to the destination Supabase project. The report distinguishes API password acceptance from readback verification (`applied_with_unverified_secrets`). Test an authentication email on the destination; API acceptance does not validate the password with the email provider.
|
|
317
|
+
|
|
318
|
+
If a 1.0.8 restore stopped at `Configuration verification failed: smtp_pass` and its `report.json` confirms Database and Edge Functions, reuse the same backup and project: run `npx smoonb@1.0.9 restore`, answer **no** for Database and Edge Functions, and **yes** for the pending Auth, Storage, Database Settings and Realtime components. Enter the provider SMTP password when prompted. This specific failure does not require another backup or a database reload.
|
|
319
|
+
|
|
320
|
+
Legacy Realtime captures may be stale: restoration reproduces the recorded values and date, not the source project's current settings. New backups capture Realtime directly through the API. Legacy function backups without the JWT flag default to requiring JWT; new backups preserve that flag.
|
|
317
321
|
|
|
318
322
|
Reports are saved after each stage, including failures. External services cannot share the database transaction: earlier stages remain completed if a later stage fails. Do not reload the database into a populated destination. Resolve the reported stage and select only pending components, or use another empty project.
|
|
319
323
|
|
package/README.pt-BR.md
CHANGED
|
@@ -313,7 +313,11 @@ A carga do banco usa uma transação com interrupção em erro, suspende trigger
|
|
|
313
313
|
|
|
314
314
|
O remapeamento opcional troca apenas o domínio Supabase do projeto em URLs, inclusive em JSON e arquivos de texto. Não troca IDs isolados nem nomes de arquivos. URLs históricas também podem ser afetadas; escolha preservar quando isso for necessário. URLs assinadas, domínios personalizados, integrações externas e chaves de API da aplicação precisam de validação específica. As chaves de plataforma do destino não são substituídas.
|
|
315
315
|
|
|
316
|
-
Auth e Realtime são
|
|
316
|
+
As configurações legíveis de Auth e Realtime são aplicadas pela Management API e conferidas por nova leitura. A **senha SMTP não é exportável**: o Supabase devolve um valor protegido, que não pode ser usado como senha em outro projeto. A partir da versão 1.0.9, o smoonb solicita a senha real do provedor em campo oculto **antes de restaurar os componentes**, inclusive ao usar backups antigos. Ela permanece somente na memória e é enviada ao Supabase de destino. O relatório distingue senha aceita pela API de configurações verificadas por leitura (`applied_with_unverified_secrets`). Teste o envio de e-mail de autenticação no destino; aceitar uma senha pela API não comprova sua validade junto ao provedor.
|
|
317
|
+
|
|
318
|
+
Se uma restauração 1.0.8 parou em `Configuration verification failed: smtp_pass` e o `report.json` confirmou Database e Edge Functions, use o mesmo backup e projeto: execute `npx smoonb@1.0.9 restore`, responda **não** para Database e Edge Functions e **sim** para Auth, Storage, Database Settings e Realtime que ficaram pendentes. Informe a senha SMTP do provedor quando solicitada. Não é necessário refazer o backup nem recarregar o banco nesse caso.
|
|
319
|
+
|
|
320
|
+
A captura antiga de Realtime pode conter valores desatualizados: a restauração reproduz os valores e a data registrados, não o estado atual do projeto original. Novos backups capturam Realtime diretamente pela API. Em backups antigos sem o sinalizador de JWT de Functions, o padrão seguro é exigir JWT; os novos backups guardam esse sinalizador.
|
|
317
321
|
|
|
318
322
|
O relatório é salvo durante cada etapa, inclusive em falhas. Serviços externos não participam da transação do banco: se uma etapa posterior falhar, as anteriores permanecem registradas como concluídas. Não repita a carga do banco em um destino já preenchido. Resolva a etapa indicada e selecione apenas os componentes pendentes, ou use outro projeto vazio.
|
|
319
323
|
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { writeJson } = require('../../../utils/fsx');
|
|
4
4
|
const { t } = require('../../../i18n');
|
|
5
|
+
const { authSnapshot } = require('../../../utils/auth-secrets');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 4: Backup Auth Settings via Management API
|
|
@@ -26,15 +27,18 @@ module.exports = async ({ projectId, accessToken, backupDir }) => {
|
|
|
26
27
|
|
|
27
28
|
const authSettings = await authResponse.json();
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
const snapshot = authSnapshot(authSettings);
|
|
31
|
+
// Never store the opaque SMTP readback as a recoverable password.
|
|
30
32
|
const authSettingsPath = path.join(backupDir, 'auth-settings.json');
|
|
31
33
|
await writeJson(authSettingsPath, {
|
|
32
34
|
project_id: projectId,
|
|
33
35
|
timestamp: new Date().toISOString(),
|
|
34
|
-
|
|
36
|
+
...snapshot
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
console.log(chalk.green(`✅ Auth Settings exportadas: ${path.basename(authSettingsPath)}`));
|
|
40
|
+
if (snapshot.secrets.smtp_pass.requires_input)
|
|
41
|
+
console.log(chalk.yellow(getT('backup.smtp.passwordNotExported')));
|
|
38
42
|
return { success: true };
|
|
39
43
|
|
|
40
44
|
} catch (error) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const { t } = require('../../i18n');
|
|
5
|
+
const { remapServiceUrls } = require('./dump');
|
|
6
|
+
const { isMasked } = require('../../utils/auth-secrets');
|
|
7
|
+
|
|
8
|
+
async function prepareAuth(
|
|
9
|
+
{ backupPath, targetProject, remapUrls = true },
|
|
10
|
+
{
|
|
11
|
+
prompt = (questions) => inquirer.prompt(questions),
|
|
12
|
+
interactive = Boolean(process.stdin.isTTY),
|
|
13
|
+
log = console.log,
|
|
14
|
+
} = {},
|
|
15
|
+
) {
|
|
16
|
+
const getT = global.smoonbI18n?.t || t;
|
|
17
|
+
const backup = JSON.parse(
|
|
18
|
+
fs.readFileSync(path.join(backupPath, 'auth-settings.json'), 'utf8'),
|
|
19
|
+
);
|
|
20
|
+
const settings =
|
|
21
|
+
backup.settings?.auth_url_config ||
|
|
22
|
+
backup.settings ||
|
|
23
|
+
backup.auth_url_config;
|
|
24
|
+
if (!settings || !Object.keys(settings).length)
|
|
25
|
+
throw new Error('Auth configuration missing from backup');
|
|
26
|
+
// Never replay an opaque value from a legacy backup as a password.
|
|
27
|
+
const { smtp_pass: stored, ...readable } = settings;
|
|
28
|
+
const desired = remapUrls
|
|
29
|
+
? JSON.parse(
|
|
30
|
+
remapServiceUrls(
|
|
31
|
+
JSON.stringify(readable),
|
|
32
|
+
backup.project_id,
|
|
33
|
+
targetProject.targetProjectId,
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
: readable;
|
|
37
|
+
const needsPassword = Boolean(
|
|
38
|
+
stored || settings.smtp_host || backup.secrets?.smtp_pass?.requires_input,
|
|
39
|
+
);
|
|
40
|
+
const writeOnly = {};
|
|
41
|
+
if (needsPassword) {
|
|
42
|
+
log(getT('restore.smtp.explanation'));
|
|
43
|
+
if (!interactive) throw new Error(getT('restore.smtp.interactiveRequired'));
|
|
44
|
+
const validate = (value) =>
|
|
45
|
+
typeof value === 'string' &&
|
|
46
|
+
value.trim().length > 0 &&
|
|
47
|
+
!isMasked(value) &&
|
|
48
|
+
value !== stored;
|
|
49
|
+
const answer = await prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'password',
|
|
52
|
+
name: 'smtpPassword',
|
|
53
|
+
mask: '*',
|
|
54
|
+
message: getT('restore.smtp.password'),
|
|
55
|
+
validate: (value) => validate(value) || getT('restore.smtp.required'),
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
if (!validate(answer.smtpPassword))
|
|
59
|
+
throw new Error(getT('restore.smtp.required'));
|
|
60
|
+
writeOnly.smtp_pass = answer.smtpPassword;
|
|
61
|
+
}
|
|
62
|
+
return { desired, writeOnly };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { prepareAuth };
|
|
@@ -188,7 +188,10 @@ module.exports = async (options = {}) => {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
if (restoreResults.authSettings) {
|
|
191
|
-
|
|
191
|
+
if (restoreResults.authSettings.verified)
|
|
192
|
+
ui.success(`🔐 ${getT('restore.index.authSettingsRestored')}`);
|
|
193
|
+
else
|
|
194
|
+
ui.warn(getT('restore.smtp.accepted'));
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
if (restoreResults.storage) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { isMasked } = require('../../utils/auth-secrets');
|
|
2
|
+
|
|
1
3
|
async function management(
|
|
2
4
|
target,
|
|
3
5
|
endpoint,
|
|
@@ -33,9 +35,29 @@ async function applyConfig(
|
|
|
33
35
|
desired,
|
|
34
36
|
allowed,
|
|
35
37
|
request = management,
|
|
38
|
+
{ writeOnly = {} } = {},
|
|
36
39
|
) {
|
|
40
|
+
const secretFields = Object.keys(writeOnly);
|
|
41
|
+
for (const key of secretFields) {
|
|
42
|
+
if (
|
|
43
|
+
endpoint !== 'config/auth' ||
|
|
44
|
+
key !== 'smtp_pass' ||
|
|
45
|
+
!allowed.includes(key)
|
|
46
|
+
)
|
|
47
|
+
throw new Error(`Unsupported write-only configuration: ${key}`);
|
|
48
|
+
if (
|
|
49
|
+
typeof writeOnly[key] !== 'string' ||
|
|
50
|
+
!writeOnly[key].trim() ||
|
|
51
|
+
isMasked(writeOnly[key])
|
|
52
|
+
)
|
|
53
|
+
throw new Error(`A real credential is required: ${key}`);
|
|
54
|
+
}
|
|
55
|
+
if (endpoint === 'config/auth' && Object.hasOwn(desired, 'smtp_pass'))
|
|
56
|
+
throw new Error(
|
|
57
|
+
'SMTP password must be supplied separately, never copied from a configuration response',
|
|
58
|
+
);
|
|
37
59
|
const before = await request(target, endpoint);
|
|
38
|
-
const patch = {};
|
|
60
|
+
const patch = { ...writeOnly };
|
|
39
61
|
const unsupported = [];
|
|
40
62
|
for (const [key, value] of Object.entries(desired)) {
|
|
41
63
|
if (JSON.stringify(before[key]) === JSON.stringify(value)) continue;
|
|
@@ -43,10 +65,7 @@ async function applyConfig(
|
|
|
43
65
|
unsupported.push(key);
|
|
44
66
|
continue;
|
|
45
67
|
}
|
|
46
|
-
if (
|
|
47
|
-
typeof value === 'string' &&
|
|
48
|
-
/^(\*{3,}|[•●]{3,}|\[redacted\])$/i.test(value)
|
|
49
|
-
)
|
|
68
|
+
if (isMasked(value))
|
|
50
69
|
throw new Error(`Masked configuration cannot be restored: ${key}`);
|
|
51
70
|
patch[key] = value;
|
|
52
71
|
}
|
|
@@ -66,7 +85,10 @@ async function applyConfig(
|
|
|
66
85
|
);
|
|
67
86
|
return {
|
|
68
87
|
success: true,
|
|
69
|
-
verified:
|
|
88
|
+
verified: secretFields.length === 0,
|
|
89
|
+
configuration_readback_verified: true,
|
|
90
|
+
write_only_fields_accepted: secretFields,
|
|
91
|
+
...(secretFields.length ? { email_delivery_verified: false } : {}),
|
|
70
92
|
fields_verified: Object.keys(desired).length,
|
|
71
93
|
fields_updated: Object.keys(patch),
|
|
72
94
|
};
|
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const { t } = require('../../i18n');
|
|
4
4
|
const { buildRestore, readDump } = require('./dump');
|
|
5
5
|
const { storagePlan } = require('./steps/06-storage');
|
|
6
|
+
const { prepareAuth } = require('./auth');
|
|
6
7
|
const steps = {
|
|
7
8
|
database: require('./steps/03-database'),
|
|
8
9
|
edgeFunctions: require('./steps/04-edge-functions'),
|
|
@@ -21,6 +22,7 @@ async function runRestore(
|
|
|
21
22
|
remapUrls = true,
|
|
22
23
|
},
|
|
23
24
|
implementations = steps,
|
|
25
|
+
{ prepareAuthConfig = prepareAuth } = {},
|
|
24
26
|
) {
|
|
25
27
|
if (selectedBackup.projectId === targetProject.targetProjectId)
|
|
26
28
|
throw new Error('Restore requires a different, empty destination project');
|
|
@@ -84,24 +86,45 @@ async function runRestore(
|
|
|
84
86
|
buildRestore(readDump(context.backupFilePath), context);
|
|
85
87
|
if (components.storage)
|
|
86
88
|
storagePlan(context.backupPath, context.backupFilePath);
|
|
89
|
+
// Resolve write-only credentials before restoring the database or any service.
|
|
90
|
+
if (components.authSettings)
|
|
91
|
+
context.authConfig = await prepareAuthConfig(context);
|
|
87
92
|
for (const [name, step] of Object.entries(implementations)) {
|
|
88
93
|
if (!components[name]) continue;
|
|
89
94
|
current = name;
|
|
90
95
|
report.results[name] = { success: false, status: 'running' };
|
|
91
96
|
save();
|
|
92
97
|
const labels = {
|
|
93
|
-
database: 'restoringDatabase',
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
database: 'restoringDatabase',
|
|
99
|
+
edgeFunctions: 'restoringEdgeFunctions',
|
|
100
|
+
authSettings: 'restoringAuthSettings',
|
|
101
|
+
storage: 'restoringStorageBuckets',
|
|
102
|
+
databaseSettings: 'restoringDatabaseSettings',
|
|
103
|
+
realtimeSettings: 'restoringRealtimeSettings',
|
|
96
104
|
};
|
|
97
105
|
console.log((global.smoonbI18n?.t || t)(`restore.index.${labels[name]}`));
|
|
98
106
|
const result = await step(context);
|
|
99
|
-
|
|
107
|
+
const acceptedSmtp =
|
|
108
|
+
name === 'authSettings' &&
|
|
109
|
+
result?.configuration_readback_verified === true &&
|
|
110
|
+
result?.email_delivery_verified === false &&
|
|
111
|
+
result?.write_only_fields_accepted?.length === 1 &&
|
|
112
|
+
result.write_only_fields_accepted[0] === 'smtp_pass';
|
|
113
|
+
if (!result?.success || (!result?.verified && !acceptedSmtp))
|
|
100
114
|
throw new Error(`Component ${name} did not pass verification`);
|
|
101
|
-
report.results[name] = {
|
|
115
|
+
report.results[name] = {
|
|
116
|
+
...result,
|
|
117
|
+
status: result.verified
|
|
118
|
+
? 'verified'
|
|
119
|
+
: 'applied_with_unverified_secrets',
|
|
120
|
+
};
|
|
102
121
|
save();
|
|
122
|
+
if (acceptedSmtp)
|
|
123
|
+
console.log((global.smoonbI18n?.t || t)('restore.smtp.accepted'));
|
|
103
124
|
}
|
|
104
|
-
report.status =
|
|
125
|
+
report.status = Object.values(report.results).every((r) => r.verified)
|
|
126
|
+
? 'verified'
|
|
127
|
+
: 'applied_with_unverified_secrets';
|
|
105
128
|
report.completed_at = new Date().toISOString();
|
|
106
129
|
save();
|
|
107
130
|
return report.results;
|
|
@@ -1,26 +1,15 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
1
|
const { applyConfig } = require('../management');
|
|
4
|
-
const {
|
|
2
|
+
const { prepareAuth } = require('../auth');
|
|
5
3
|
const allowed = require('../auth-fields.json');
|
|
6
|
-
module.exports = async ({
|
|
7
|
-
const
|
|
8
|
-
|
|
4
|
+
module.exports = async (context, { request } = {}) => {
|
|
5
|
+
const { desired, writeOnly } =
|
|
6
|
+
context.authConfig || (await prepareAuth(context));
|
|
7
|
+
return applyConfig(
|
|
8
|
+
context.targetProject,
|
|
9
|
+
'config/auth',
|
|
10
|
+
desired,
|
|
11
|
+
allowed,
|
|
12
|
+
request,
|
|
13
|
+
{ writeOnly },
|
|
9
14
|
);
|
|
10
|
-
const settings =
|
|
11
|
-
backup.settings?.auth_url_config ||
|
|
12
|
-
backup.settings ||
|
|
13
|
-
backup.auth_url_config;
|
|
14
|
-
if (!settings || !Object.keys(settings).length)
|
|
15
|
-
throw new Error('Auth configuration missing from backup');
|
|
16
|
-
const desired = remapUrls
|
|
17
|
-
? JSON.parse(
|
|
18
|
-
remapServiceUrls(
|
|
19
|
-
JSON.stringify(settings),
|
|
20
|
-
backup.project_id,
|
|
21
|
-
targetProject.targetProjectId,
|
|
22
|
-
),
|
|
23
|
-
)
|
|
24
|
-
: settings;
|
|
25
|
-
return applyConfig(targetProject, 'config/auth', desired, allowed);
|
|
26
15
|
};
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"prompt.confirmYes": "(Y/n)",
|
|
19
19
|
"prompt.confirmNo": "(y/N)",
|
|
20
20
|
"prompt.cancelled": "Operation cancelled.",
|
|
21
|
+
"backup.smtp.passwordNotExported": "⚠️ Supabase does not export the SMTP password. Keep it in your password manager: restoring Auth will require it. You do not need to create another backup.",
|
|
22
|
+
"restore.smtp.explanation": "The backup contains SMTP settings, but Supabase does not let you retrieve the password. Enter your email provider password (or the key used as the SMTP password). It will be sent only to the destination Supabase project and will not be saved by smoonb.",
|
|
23
|
+
"restore.smtp.password": "Email provider SMTP password:",
|
|
24
|
+
"restore.smtp.required": "Enter the real provider password, not the protected value in auth-settings.json. Use Ctrl+C to cancel before restoring any components.",
|
|
25
|
+
"restore.smtp.interactiveRequired": "Restoring Auth requires the SMTP password. Run in an interactive terminal to enter it securely before any components are restored.",
|
|
26
|
+
"restore.smtp.accepted": "⚠️ Auth: settings checked; SMTP password accepted by the API. Supabase does not allow password readback verification. Test an authentication email before using the project.",
|
|
21
27
|
|
|
22
28
|
"error.generic": "Something went wrong.",
|
|
23
29
|
"error.pathNotFound": "Path not found: {path}",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"prompt.confirmYes": "(S/n)",
|
|
19
19
|
"prompt.confirmNo": "(s/N)",
|
|
20
20
|
"prompt.cancelled": "Operação cancelada.",
|
|
21
|
+
"backup.smtp.passwordNotExported": "⚠️ O Supabase não exporta a senha SMTP. Guarde-a no seu gerenciador de senhas: ela será solicitada ao restaurar Auth. Não é necessário refazer o backup.",
|
|
22
|
+
"restore.smtp.explanation": "O backup contém as configurações SMTP, mas o Supabase não permite recuperar a senha. Informe a senha do provedor de e-mail (ou a chave usada como senha SMTP). Ela será enviada somente ao Supabase de destino e não será gravada pelo smoonb.",
|
|
23
|
+
"restore.smtp.password": "Senha SMTP do provedor de e-mail:",
|
|
24
|
+
"restore.smtp.required": "Informe a senha real do provedor, não o valor protegido de auth-settings.json. Use Ctrl+C para cancelar antes de restaurar os componentes.",
|
|
25
|
+
"restore.smtp.interactiveRequired": "A restauração de Auth precisa da senha SMTP. Execute em um terminal interativo para informá-la de forma oculta antes de restaurar os componentes.",
|
|
26
|
+
"restore.smtp.accepted": "⚠️ Auth: configurações conferidas; senha SMTP aceita pela API. O Supabase não permite conferir a senha por leitura. Teste o envio de e-mail de autenticação antes de usar o projeto.",
|
|
21
27
|
|
|
22
28
|
"error.generic": "Algo inesperado aconteceu.",
|
|
23
29
|
"error.pathNotFound": "Caminho não encontrado: {path}",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Supabase SMTP passwords are write-only. The GET response is not a
|
|
2
|
+
// transferable credential, even when it looks like an ordinary string.
|
|
3
|
+
// https://github.com/supabase/supabase/blob/master/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx
|
|
4
|
+
function authSnapshot(settings) {
|
|
5
|
+
const { smtp_pass: smtpPass, ...readable } = settings;
|
|
6
|
+
return {
|
|
7
|
+
settings: readable,
|
|
8
|
+
secrets: {
|
|
9
|
+
smtp_pass: {
|
|
10
|
+
exportable: false,
|
|
11
|
+
requires_input: Boolean(smtpPass || settings.smtp_host),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isMasked(value) {
|
|
18
|
+
return (
|
|
19
|
+
typeof value === 'string' && /^(\*{3,}|[•●]{3,}|\[redacted\])$/i.test(value)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { authSnapshot, isMasked };
|