mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
fix: harden API key authentication
This commit is contained in:
@@ -294,21 +294,58 @@ export default function SettingsPage(props: SettingsPageProps) {
|
||||
<ConfirmDialog
|
||||
open={apiKeyDialogOpen}
|
||||
title={t('txt_api_key')}
|
||||
message=""
|
||||
message={t('txt_api_key_dialog_intro')}
|
||||
hideCancel
|
||||
confirmText={t('txt_close')}
|
||||
onConfirm={() => setApiKeyDialogOpen(false)}
|
||||
onCancel={() => setApiKeyDialogOpen(false)}
|
||||
>
|
||||
<div className="stack" style={{ gap: 8, marginTop: 4 }}>
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid color-mix(in srgb, var(--danger) 24%, transparent)',
|
||||
background: 'color-mix(in srgb, var(--danger) 7%, var(--surface))',
|
||||
borderRadius: 8,
|
||||
padding: 14,
|
||||
marginTop: 12,
|
||||
marginBottom: 14,
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 800, color: 'var(--danger)', marginBottom: 8 }}>{t('txt_warning')}</div>
|
||||
<div style={{ color: 'var(--text)', lineHeight: 1.55 }}>{t('txt_api_key_warning_body')}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid color-mix(in srgb, var(--primary) 25%, transparent)',
|
||||
background: 'color-mix(in srgb, var(--primary) 7%, var(--surface))',
|
||||
borderRadius: 8,
|
||||
padding: 14,
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontWeight: 800, color: 'var(--primary)', marginBottom: 10 }}>
|
||||
<KeyRound size={15} />
|
||||
<span>{t('txt_oauth_client_credentials')}</span>
|
||||
</div>
|
||||
{([
|
||||
[t('txt_client_id'), `user.${props.profile.id}`],
|
||||
[t('txt_client_secret'), apiKey],
|
||||
[t('txt_scope'), 'api'],
|
||||
[t('txt_grant_type'), 'client_credentials'],
|
||||
] as [string, string][]).map(([label, value]) => (
|
||||
<label key={label} className="field">
|
||||
<span>{label}</span>
|
||||
<input className="input" readOnly value={value} onFocus={(e) => (e.currentTarget as HTMLInputElement).select()} />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 8 }}>
|
||||
<input className="input" readOnly value={value} onFocus={(e) => (e.currentTarget as HTMLInputElement).select()} />
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary small"
|
||||
onClick={() => void copyTextToClipboard(value, { successMessage: t('txt_copied') })}
|
||||
>
|
||||
<Clipboard size={14} className="btn-icon" />
|
||||
{t('txt_copy')}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -596,7 +596,7 @@ export async function deleteAllAuthorizedDevices(authedFetch: AuthedFetch): Prom
|
||||
}
|
||||
|
||||
export async function getApiKey(authedFetch: AuthedFetch, masterPasswordHash: string): Promise<string> {
|
||||
const resp = await authedFetch('/api/accounts/api_key', {
|
||||
const resp = await authedFetch('/api/accounts/api-key', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ masterPasswordHash }),
|
||||
@@ -610,7 +610,7 @@ export async function getApiKey(authedFetch: AuthedFetch, masterPasswordHash: st
|
||||
}
|
||||
|
||||
export async function rotateApiKey(authedFetch: AuthedFetch, masterPasswordHash: string): Promise<string> {
|
||||
const resp = await authedFetch('/api/accounts/rotate_api_key', {
|
||||
const resp = await authedFetch('/api/accounts/rotate-api-key', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ masterPasswordHash }),
|
||||
|
||||
@@ -609,9 +609,13 @@ const messages: Record<Locale, Record<string, string>> = {
|
||||
txt_api_key_rotated: "API key rotated",
|
||||
txt_rotate_api_key_confirm: "Rotate API key? The current key will stop working immediately.",
|
||||
txt_api_key_is_empty: "API key is empty",
|
||||
txt_api_key_dialog_intro: "Your API key can be used to authenticate with the Bitwarden CLI.",
|
||||
txt_api_key_warning_body: "Your API key is an alternative authentication mechanism. Keep it secret.",
|
||||
txt_oauth_client_credentials: "OAuth 2.0 Client Credentials",
|
||||
txt_client_id: "client_id",
|
||||
txt_client_secret: "client_secret",
|
||||
txt_scope: "scope",
|
||||
txt_grant_type: "grant_type",
|
||||
txt_refresh: "Refresh",
|
||||
txt_refresh_in_seconds_s: "Refresh in {seconds}s",
|
||||
txt_regenerate: "Regenerate",
|
||||
@@ -1382,9 +1386,13 @@ const zhCNOverrides: Record<string, string> = {
|
||||
txt_api_key_rotated: 'API 密钥已轮换',
|
||||
txt_rotate_api_key_confirm: '轮换 API 密钥?当前密钥将立即失效。',
|
||||
txt_api_key_is_empty: 'API 密钥为空',
|
||||
txt_api_key_dialog_intro: '您的 API 密钥可用于在 Bitwarden CLI 中进行身份验证。',
|
||||
txt_api_key_warning_body: '您的 API 密钥是一种替代身份验证机制。请严格保密。',
|
||||
txt_oauth_client_credentials: 'OAuth 2.0 客户端凭据',
|
||||
txt_client_id: 'client_id',
|
||||
txt_client_secret: 'client_secret',
|
||||
txt_scope: 'scope',
|
||||
txt_grant_type: 'grant_type',
|
||||
txt_refresh_in_seconds_s: '{seconds} 秒后刷新',
|
||||
txt_registration_succeeded_please_sign_in: '注册成功,请登录',
|
||||
txt_remove_device: '移除设备',
|
||||
|
||||
Reference in New Issue
Block a user