Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions components/access-keys/new-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useAccessKeys } from "@/hooks/use-access-keys"
import { useApi } from "@/contexts/api-context"
import { makeRandomString } from "@/lib/functions"
import { cn } from "@/lib/utils"
import { isSecretKeyValid } from "@/lib/secret-key"

interface AccessKeysNewItemProps {
visible: boolean
Expand Down Expand Up @@ -105,8 +106,8 @@ export function AccessKeysNewItem({ visible, onVisibleChange, onSuccess, onNotic
}
if (!secretKey) {
newErrors.secretKey = t("Please enter Secret Key")
} else if (secretKey.length < 8 || secretKey.length > 40) {
newErrors.secretKey = t("Secret Key length must be between 8 and 40 characters")
} else if (!isSecretKeyValid(secretKey)) {
newErrors.secretKey = t("Secret Key must be at least 8 bytes")
}
if (!name) {
newErrors.name = t("Please enter name")
Expand Down
11 changes: 4 additions & 7 deletions components/account/change-password-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from "@
import { Alert, AlertDescription } from "@/components/ui/alert"
import { useAccount } from "@/hooks/use-account"
import { useMessage } from "@/lib/feedback/message"

/** Mirrors the server's `SECRET_KEY_MIN_LEN`. */
const PASSWORD_MIN_LENGTH = 8
import { isSecretKeyValid } from "@/lib/secret-key"

interface ChangePasswordDialogProps {
open: boolean
Expand Down Expand Up @@ -66,8 +64,8 @@ export function ChangePasswordDialog({ open, onOpenChange, onChanged }: ChangePa
current: current ? "" : t("Please enter your current password"),
next: !next
? t("Please enter new password")
: next.length < PASSWORD_MIN_LENGTH
? t("Password must be at least 8 characters")
: !isSecretKeyValid(next)
? t("Password must be at least 8 bytes")
: next === current
? t("The new password must be different from the current one")
: "",
Expand Down Expand Up @@ -164,15 +162,14 @@ export function ChangePasswordDialog({ open, onOpenChange, onChanged }: ChangePa
type="password"
autoComplete="new-password"
spellCheck={false}
minLength={PASSWORD_MIN_LENGTH}
required
disabled={submitting}
aria-invalid={Boolean(errors.next)}
aria-describedby={errors.next ? "account-password-new-error" : "account-password-new-hint"}
/>
</FieldContent>
<FieldDescription id="account-password-new-hint">
{t("At least 8 characters. This is also your S3 secret key.")}
{t("At least 8 bytes. This is also your S3 secret key.")}
</FieldDescription>
<FieldError id="account-password-new-error">{errors.next}</FieldError>
</Field>
Expand Down
5 changes: 3 additions & 2 deletions components/user/edit-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { usePermissions } from "@/hooks/use-permissions"
import { usePolicies } from "@/hooks/use-policies"
import { useGroups } from "@/hooks/use-groups"
import { useMessage } from "@/lib/feedback/message"
import { isSecretKeyValid } from "@/lib/secret-key"
import { getAvailableUserEditTabs } from "@/lib/user-edit-tabs"
import { UserEditSecretKey } from "./edit/secret-key"
import { UserEditGroups } from "./edit/groups"
Expand Down Expand Up @@ -177,8 +178,8 @@ export function UserEditForm({ open, onOpenChange, row, onSuccess }: UserEditFor

const validate = () => {
const newErrors = { secretKey: "" }
if (secretKey && !/^.{8,40}$/.test(secretKey)) {
newErrors.secretKey = t("password length cannot be less than 8 characters and greater than 40 characters")
if (secretKey && !isSecretKeyValid(secretKey)) {
newErrors.secretKey = t("Password must be at least 8 bytes")
}
setErrors(newErrors)
return !newErrors.secretKey
Expand Down
5 changes: 3 additions & 2 deletions components/user/edit/access-keys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useDialog } from "@/lib/feedback/dialog"
import { useMessage } from "@/lib/feedback/message"
import { formatDateTime, makeRandomString } from "@/lib/functions"
import { cn } from "@/lib/utils"
import { isSecretKeyValid } from "@/lib/secret-key"
import { useTranslation } from "react-i18next"
import type { ColumnDef } from "@tanstack/react-table"

Expand Down Expand Up @@ -169,8 +170,8 @@ function UserAccessKeysNewDialog({ open, onOpenChange, userName, onSuccess, onNo

if (!secretKey) {
nextErrors.secretKey = t("Please enter Secret Key")
} else if (secretKey.length < 8 || secretKey.length > 40) {
nextErrors.secretKey = t("Secret Key length must be between 8 and 40 characters")
} else if (!isSecretKeyValid(secretKey)) {
nextErrors.secretKey = t("Secret Key must be at least 8 bytes")
}

if (!name) {
Expand Down
9 changes: 3 additions & 6 deletions components/user/new-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useUsers } from "@/hooks/use-users"
import { usePolicies } from "@/hooks/use-policies"
import { useGroups } from "@/hooks/use-groups"
import { cn } from "@/lib/utils"
import { isSecretKeyValid } from "@/lib/secret-key"

interface UserNewFormProps {
open: boolean
Expand All @@ -25,8 +26,6 @@ interface UserNewFormProps {

const USERNAME_MIN_LENGTH = 3
const USERNAME_MAX_LENGTH = 128
const PASSWORD_MIN_LENGTH = 8
const PASSWORD_MAX_LENGTH = 40

export function UserNewForm({ open, onOpenChange, onSuccess }: UserNewFormProps) {
const { t } = useTranslation()
Expand Down Expand Up @@ -115,8 +114,8 @@ export function UserNewForm({ open, onOpenChange, onSuccess }: UserNewFormProps)
}
if (!secretKey.trim()) {
newErrors.secretKey = t("Please enter password")
} else if (secretKey.length < PASSWORD_MIN_LENGTH || secretKey.length > PASSWORD_MAX_LENGTH) {
newErrors.secretKey = t("password length cannot be less than 8 characters and greater than 40 characters")
} else if (!isSecretKeyValid(secretKey)) {
newErrors.secretKey = t("Password must be at least 8 bytes")
}
setErrors(newErrors)
return !newErrors.accessKey && !newErrors.secretKey
Expand Down Expand Up @@ -218,8 +217,6 @@ export function UserNewForm({ open, onOpenChange, onSuccess }: UserNewFormProps)
value={secretKey}
onChange={(e) => setSecretKey(e.target.value)}
type="password"
minLength={PASSWORD_MIN_LENGTH}
maxLength={PASSWORD_MAX_LENGTH}
autoComplete="new-password"
spellCheck={false}
required
Expand Down
5 changes: 4 additions & 1 deletion i18n/locales/ar-MA.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "يجب أن تتكون كلمة المرور من 8 بايتات على الأقل",
"Secret Key must be at least 8 bytes": "يجب أن يتكون المفتاح السري من 8 بايتات على الأقل",
"At least 8 bytes. This is also your S3 secret key.": "8 بايتات على الأقل. هذا أيضًا مفتاح S3 السري الخاص بك."
}
5 changes: 4 additions & 1 deletion i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Das Passwort muss mindestens 8 Bytes lang sein",
"Secret Key must be at least 8 bytes": "Der geheime Schlüssel muss mindestens 8 Bytes lang sein",
"At least 8 bytes. This is also your S3 secret key.": "Mindestens 8 Bytes. Dies ist auch Ihr geheimer S3-Schlüssel."
}
5 changes: 4 additions & 1 deletion i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Password must be at least 8 bytes",
"Secret Key must be at least 8 bytes": "Secret Key must be at least 8 bytes",
"At least 8 bytes. This is also your S3 secret key.": "At least 8 bytes. This is also your S3 secret key."
}
5 changes: 4 additions & 1 deletion i18n/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "La contraseña debe tener al menos 8 bytes",
"Secret Key must be at least 8 bytes": "La clave secreta debe tener al menos 8 bytes",
"At least 8 bytes. This is also your S3 secret key.": "Al menos 8 bytes. Esta también es su clave secreta de S3."
}
5 changes: 4 additions & 1 deletion i18n/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Le mot de passe doit contenir au moins 8 octets",
"Secret Key must be at least 8 bytes": "La clé secrète doit contenir au moins 8 octets",
"At least 8 bytes. This is also your S3 secret key.": "Au moins 8 octets. Il s’agit aussi de votre clé secrète S3."
}
5 changes: 4 additions & 1 deletion i18n/locales/id-ID.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Kata sandi harus berisi minimal 8 byte",
"Secret Key must be at least 8 bytes": "Kunci rahasia harus berisi minimal 8 byte",
"At least 8 bytes. This is also your S3 secret key.": "Minimal 8 byte. Ini juga merupakan kunci rahasia S3 Anda."
}
5 changes: 4 additions & 1 deletion i18n/locales/it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "La password deve contenere almeno 8 byte",
"Secret Key must be at least 8 bytes": "La chiave segreta deve contenere almeno 8 byte",
"At least 8 bytes. This is also your S3 secret key.": "Almeno 8 byte. Questa è anche la chiave segreta S3."
}
5 changes: 4 additions & 1 deletion i18n/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "パスワードは8バイト以上必要です",
"Secret Key must be at least 8 bytes": "シークレットキーは8バイト以上必要です",
"At least 8 bytes. This is also your S3 secret key.": "8バイト以上。これはS3シークレットキーでもあります。"
}
5 changes: 4 additions & 1 deletion i18n/locales/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "비밀번호는 최소 8바이트여야 합니다",
"Secret Key must be at least 8 bytes": "시크릿 키는 최소 8바이트여야 합니다",
"At least 8 bytes. This is also your S3 secret key.": "최소 8바이트입니다. S3 시크릿 키로도 사용됩니다."
}
5 changes: 4 additions & 1 deletion i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "A senha deve ter pelo menos 8 bytes",
"Secret Key must be at least 8 bytes": "A chave secreta deve ter pelo menos 8 bytes",
"At least 8 bytes. This is also your S3 secret key.": "Pelo menos 8 bytes. Esta também é sua chave secreta S3."
}
5 changes: 4 additions & 1 deletion i18n/locales/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Пароль должен содержать не менее 8 байт",
"Secret Key must be at least 8 bytes": "Секретный ключ должен содержать не менее 8 байт",
"At least 8 bytes. This is also your S3 secret key.": "Не менее 8 байт. Это также ваш секретный ключ S3."
}
5 changes: 4 additions & 1 deletion i18n/locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Parola en az 8 bayt olmalıdır",
"Secret Key must be at least 8 bytes": "Gizli anahtar en az 8 bayt olmalıdır",
"At least 8 bytes. This is also your S3 secret key.": "En az 8 bayt. Bu aynı zamanda S3 gizli anahtarınızdır."
}
5 changes: 4 additions & 1 deletion i18n/locales/vi-VN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "You do not have permission to control backfill",
"You do not have permission to view this bucket's migration settings.": "You do not have permission to view this bucket's migration settings.",
"Your migration configuration changes will be lost.": "Your migration configuration changes will be lost.",
"pull failures": "pull failures"
"pull failures": "pull failures",
"Password must be at least 8 bytes": "Mật khẩu phải có ít nhất 8 byte",
"Secret Key must be at least 8 bytes": "Khóa bí mật phải có ít nhất 8 byte",
"At least 8 bytes. This is also your S3 secret key.": "Ít nhất 8 byte. Đây cũng là khóa bí mật S3 của bạn."
}
5 changes: 4 additions & 1 deletion i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2226,5 +2226,8 @@
"You do not have permission to control backfill": "你没有控制回填任务的权限",
"You do not have permission to view this bucket's migration settings.": "你没有查看此存储桶迁移设置的权限。",
"Your migration configuration changes will be lost.": "对迁移配置的更改将会丢失。",
"pull failures": "次拉取失败"
"pull failures": "次拉取失败",
"Password must be at least 8 bytes": "密码至少需要 8 个字节",
"Secret Key must be at least 8 bytes": "Secret Key 至少需要 8 个字节",
"At least 8 bytes. This is also your S3 secret key.": "至少 8 个字节。这也是您的 S3 Secret Key。"
}
4 changes: 4 additions & 0 deletions lib/secret-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** Matches the server's Secret Key length validation without changing the value. */
export function isSecretKeyValid(secretKey: string): boolean {
return new TextEncoder().encode(secretKey).length >= 8
}
2 changes: 1 addition & 1 deletion tests/lib/iam-remaining-safety.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ test("change password proves knowledge of the current secret before rotating it"
// change the account's credentials.
assert.match(source, /changePassword\(current, next\)/)
assert.match(source, /autoComplete="current-password"/)
assert.match(source, /PASSWORD_MIN_LENGTH = 8/)
assert.match(source, /!isSecretKeyValid\(next\)/)
assert.match(source, /<form\s+className="contents"\s+onSubmit=/)
assert.match(source, /type="submit"/)

Expand Down
24 changes: 24 additions & 0 deletions tests/lib/secret-key.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import assert from "node:assert/strict"
import test from "node:test"
import { isSecretKeyValid } from "../../lib/secret-key"

test("secret keys require at least eight UTF-8 bytes", () => {
for (const secret of ["", "1234567", "ééé"]) {
assert.equal(isSecretKeyValid(secret), false)
}
for (const secret of ["12345678", "éééé", "🔑🔑"]) {
assert.equal(isSecretKeyValid(secret), true)
}
})

test("custom secret keys have no 40 or 256 byte business limit", () => {
for (const length of [40, 41, 90, 128, 256, 257, 4096]) {
assert.equal(isSecretKeyValid("s".repeat(length)), true, `${length}-byte secret`)
}
})

test("length validation preserves whitespace and Unicode without normalization", () => {
for (const secret of [" 123456 ", "éééé", "e\u0301e\u0301e\u0301"]) {
assert.equal(isSecretKeyValid(secret), true)
}
})