Güvenlik

Bir yapay zeka ürünü için oturumlar ve güvenlik. Satırları animasyonla kaldıran oturum kapatmalı etkin cihazlar, canlı güç ölçerli parola değişikliği, gerçek QR kodlu iki aşamalı doğrulama kurulumu, 6 haneli bir kontrol ve indirilebilir kurtarma kodları, WebAuthn ile passkey'ler ve yazılı onayla korunan hesap silme.

Her yapay zeka ürününde aynı güvenlik sayfası bulunur: nerede oturum açtığınız, nasıl oturum açtığınız ve nasıl ayrılacağınız. Security, Settings bloğunun grupları ve satırları üzerine kurulu bu sayfadır; her akış sahibi olduğunuz asenkron callback'lere bağlıdır.

Active sessions her cihazı tarayıcısı, sistemi, konumu ve en son ne zaman etkin olduğuyla, sade göreli zamanla listeler. Bu cihaz işaretlenir ve buradan kapatılamaz. Bir cihazın oturumunu kapatmak düğmesinde ilerleme gösterir, ardından satır katlanır ve odak sonraki satıra geçer. Diğer her yerdeki oturumları kapatmak önce sorar ve oturumunu kapatacağı cihazları adlandırır.

Change password siz yazarken denetler: dört adımlı bir güç ölçeri ve hâlâ eksik olanların bir kontrol listesi; yazdığınız hiçbir şeyi engellemeden. Sunucunuz mevcut parolanın yanlış olduğunu söylerse mesaj o alanın altında görünür ve yazdıklarınız korunarak odak ona döner. Aynı adımda diğer oturumları kapatabilirsiniz.

İki aşamalı doğrulama kurulumu, sunucunuzun döndürdüğü otpauth bağlantısı için gerçek, taranabilir bir QR kodu çizer; taramanın mümkün olmadığı durumlar için kopyalama düğmeli kurulum anahtarını gösterir ve telefonda bağlantıyı bir kimlik doğrulayıcı uygulamada açmayı önerir. 6 haneli kod tamamlanır tamamlanmaz kendini denetler, yanlış kodda sallanır ve temizlenir, ardından kopyalanacak ya da metin dosyası olarak indirilecek kurtarma kodlarını gösterir. Kapatmak önce sorar.

Passkeys, WebAuthn akışınızı çağırır ve tarayıcının yanıtlarını ele alır: istemi kapatmak hiçbir şey eklemez ve hata göstermez, zaten passkey'i olan bir cihaz bunu söyler. Hesabı silmek tam ifadeyi ister, birlikte gidenleri listeler ve sunucu hayır derse iletişim kutusunu mesajınızla açık tutar.

  1. Pro registry'yi components.json dosyasına ekleyin

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token'ınızı ekleyin

    Hesap sayfanızda bir token oluşturun ve .env.local dosyasına HEXTAUI_PRO_TOKEN olarak ekleyin.

  3. Bloğu ekleyin

    pnpm dlx shadcn@latest add @hextaui-pro/security

API'nize bağlayın

Hesabın şu anki durumunu ve her eylem için asenkron bir callback iletin. Sunucu bitirince resolve edin ve veriyi yenileyin; her parça kendi ilerlemesini gösterir. Hata mesajınızı yerinde göstermek için hata fırlatın; kişinin girdisi korunur.

"use client"

import * as React from "react"
import { IconShieldLock } from "@tabler/icons-react"

import { SettingsSection, SettingsShell, type SettingsSectionItem } from "../settings/settings"
import { SecuritySettings, type SecurityPasskey, type SecuritySession } from "@/components/blocks/security/security"

const sections: SettingsSectionItem[] = [
  { id: "security", label: "Security", icon: <IconShieldLock />, keywords: ["password", "2fa", "sessions"] },
]

type Account = {
  email: string
  sessions: SecuritySession[]
  passkeys: SecurityPasskey[]
  passwordChangedAt: string | null
  twoFactor: boolean
}

async function call<T = void>(path: string, init?: RequestInit): Promise<T> {
  const response = await fetch(path, { headers: { "content-type": "application/json" }, ...init })
  if (!response.ok) {
    const problem = await response.json().catch(() => null)
    throw new Error(problem?.detail ?? "Check your connection and try again.")
  }
  return response.status === 204 ? (undefined as T) : response.json()
}

export function SecurityPage({ initial }: { initial: Account }) {
  const [account, setAccount] = React.useState(initial)
  const refresh = async () => setAccount(await call<Account>("/api/account/security"))

  return (
    <SettingsShell sections={sections} className="h-svh">
      <SettingsSection id="security">
        <SecuritySettings
          email={account.email}
          sessions={account.sessions}
          onSignOutSession={async (id) => {
            await call(`/api/sessions/${id}`, { method: "DELETE" })
            await refresh()
          }}
          onSignOutOtherSessions={async () => {
            await call("/api/sessions/others", { method: "DELETE" })
            await refresh()
          }}
          passwordChangedAt={account.passwordChangedAt}
          onChangePassword={async (change) => {
            const response = await fetch("/api/password", {
              method: "PUT",
              body: JSON.stringify(change),
            })
            if (response.status === 403) return { currentPassword: "That password is incorrect." }
            if (!response.ok) throw new Error("Couldn’t change your password. Try again.")
            await refresh()
          }}
          twoFactorEnabled={account.twoFactor}
          onStartTwoFactor={() => call<{ uri: string }>("/api/2fa/setup", { method: "POST" })}
          onVerifyTwoFactor={async (code) => {
            const { recoveryCodes } = await call<{ recoveryCodes: string[] }>("/api/2fa/verify", {
              method: "POST",
              body: JSON.stringify({ code }),
            })
            await refresh()
            return recoveryCodes
          }}
          onDisableTwoFactor={async () => {
            await call("/api/2fa", { method: "DELETE" })
            await refresh()
          }}
          passkeys={account.passkeys}
          onAddPasskey={async () => {
            await registerPasskey()
            await refresh()
          }}
          onRemovePasskey={async (id) => {
            await call(`/api/passkeys/${id}`, { method: "DELETE" })
            await refresh()
          }}
          onDeleteAccount={async () => {
            await call("/api/account", { method: "DELETE" })
            window.location.assign("/")
          }}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

async function registerPasskey() {
  const options = await call<PublicKeyCredentialCreationOptionsJSON>("/api/passkeys/options", {
    method: "POST",
  })
  const credential = (await navigator.credentials.create({
    publicKey: PublicKeyCredential.parseCreationOptionsFromJSON(options),
  })) as PublicKeyCredential
  await call("/api/passkeys", { method: "POST", body: JSON.stringify(credential.toJSON()) })
}

Parçaları tek başına kullanın

SecuritySessions, SecurityPasswordRow, SecurityTwoFactorRow, SecurityPasskeys ve SecurityDeleteAccount tek başlarına çalışır; böylece onları herhangi bir bölüme koyabilirsiniz. Alan hatalarını alanın altında göstermek için onChangePassword'den döndürün.

"use client"

import { SettingsGroup, SettingsSection } from "../settings/settings"
import {
  SecurityDeleteAccount,
  SecurityPasswordRow,
  SecuritySessions,
  SecurityTwoFactorRow,
  type SecuritySession,
} from "@/components/blocks/security/security"

export function AccountSection({
  email,
  sessions,
  twoFactor,
  refresh,
}: {
  email: string
  sessions: SecuritySession[]
  twoFactor: boolean
  refresh: () => Promise<void>
}) {
  return (
    <SettingsSection id="account">
      <SettingsGroup title="Sign-in">
        <SecurityPasswordRow
          email={email}
          onChangePassword={async ({ currentPassword, newPassword, signOutOthers }) => {
            const response = await fetch("/api/password", {
              method: "PUT",
              body: JSON.stringify({ currentPassword, newPassword, signOutOthers }),
            })
            if (response.status === 403) return { currentPassword: "That password is incorrect." }
            if (response.status === 422) return { newPassword: "That password showed up in a data breach." }
            if (!response.ok) throw new Error("Couldn’t change your password. Try again.")
          }}
        />
        <SecurityTwoFactorRow
          email={email}
          enabled={twoFactor}
          onStart={() => fetch("/api/2fa/setup", { method: "POST" }).then((response) => response.json())}
          onVerify={async (code) => {
            const response = await fetch("/api/2fa/verify", { method: "POST", body: JSON.stringify({ code }) })
            if (!response.ok) throw new Error("That code didn’t work. Try the newest one.")
            const { recoveryCodes } = await response.json()
            await refresh()
            return recoveryCodes
          }}
          onDisable={() => fetch("/api/2fa", { method: "DELETE" }).then(refresh)}
        />
      </SettingsGroup>
      <SecuritySessions
        title="Where you’re signed in"
        sessions={sessions}
        onSignOut={(id) => fetch(`/api/sessions/${id}`, { method: "DELETE" }).then(refresh)}
        onSignOutOthers={() => fetch("/api/sessions/others", { method: "DELETE" }).then(refresh)}
      />
      <SecurityDeleteAccount
        email={email}
        consequences={["Your workspace and its 3 projects", "Your Team plan, canceled right away"]}
        onDelete={async () => {
          const response = await fetch("/api/account", { method: "DELETE" })
          if (!response.ok) throw new Error("Couldn’t delete your account. Nothing was deleted.")
          window.location.assign("/")
        }}
      />
    </SettingsSection>
  )
}

Yapı

Dıştan içe doğru birleştirdiğiniz parçalar.

ParçaAçıklama
SecuritySettingsAşağıdakilerin hepsi tek çağrıda. Her parça, ihtiyaç duyduğu veriyi ve callback'leri ilettiğinizde görünür.
SecuritySessionsCihaz başına ve diğer tüm oturumlar için oturum kapatmalı, açık cihazları listeleyen bir grup.
SecurityPasswordRowParola değiştirme iletişim kutusunu açan bir satır.
SecurityTwoFactorRowİki aşamalı doğrulama durumu, kurulum iletişim kutusu ve kapatma onayı olan bir satır.
SecurityPasskeysEklemeli ve kaldırmalı, passkey'leri listeleyen bir grup.
SecurityDeleteAccountHesap silme satırı ve yazılı onayıyla bir grup.
measurePasswordParola iletişim kutusunun kullandığı güç denetimi, kendi formlarınız için.
PropTürVarsayılan
emailHesabın e-postası. Silme ifadesinde, parola yöneticisi ipucunda ve kurtarma kodları dosyasında kullanılır.
string–
sessions{ id, browser, os, device?, location?, lastActive, current? }. device "desktop", "laptop", "phone" veya "tablet" olabilir.
SecuritySession[]–
onSignOutSessionBir oturumu kapatır. Resolve olunca sessions'tan kaldırın ve satır katlanır.
(id: string) => Promise<void>–
onSignOutOtherSessionsGeçerli olan dışındaki her oturumu kapatır. Kişi onayladıktan sonra çalışır.
() => Promise<void>–
passwordChangedAtPassword altında Last changed olarak gösterilir.
Date | string | number | null–
onChangePasswordBir mesajı o alanın altında göstermek için { currentPassword } veya { newPassword } döndürün ya da düğmelerin üstünde göstermek için hata fırlatın.
({ currentPassword, newPassword, signOutOthers }) => Promise<void | errors>–
twoFactorEnabledİki aşamalı doğrulamanın açık olup olmadığı.
booleanfalse
onStartTwoFactorKurulum iletişim kutusu açıldığında bekleyen bir gizli anahtar oluşturur. uri, QR kodu olarak çizilen otpauth bağlantısıdır; secret varsayılan olarak uri içindekidir.
() => Promise<{ uri, secret?, recoveryCodes? }>–
onVerifyTwoFactor6 haneli kodu denetler ve iki aşamalı doğrulamayı açar. Kurtarma kodlarını göstermek için döndürün ya da kod yanlışsa hata fırlatın.
(code: string) => Promise<void | string[]>–
onDisableTwoFactorİki aşamalı doğrulamayı kapatır. Kişi onayladıktan sonra çalışır.
() => Promise<void>–
passkeys{ id, name, createdAt, lastUsed? }.
SecurityPasskey[]–
onAddPasskeyWebAuthn kaydınızı çalıştırır. NotAllowedError veya AbortError iptal sayılır ve hiçbir şey göstermez.
() => Promise<void>–
onRemovePasskeyBir passkey'i kaldırır. Kişi onayladıktan sonra çalışır.
(id: string) => Promise<void>–
passkeysSupportedWebAuthn desteği denetimini geçersiz kılar.
booleandetected
onDeleteAccountHesabı siler. Kişi ifadeyi yazdıktan sonra çalışır. İletişim kutusunu mesajınızla açık tutmak için hata fırlatın.
() => Promise<void>–
deleteConsequencesSilinenler, onayda listelenir.
ReactNode[]–

SecuritySessions

Ayrıca her SettingsGroup prop'unu kabul eder.

PropTürVarsayılan
sessionsOturum açık cihazlar.
SecuritySession[]–
onSignOutBir oturumu kapatır.
(id: string) => Promise<void>–
onSignOutOthersDiğer her oturumu kapatır.
() => Promise<void>–
titleGrup başlığı.
ReactNode"Active sessions"
PropTürVarsayılan
emailParola yöneticileri doğru girişi güncellesin diye gizli bir kullanıcı adı alanına doldurulur.
string–
changedAtParolanın en son değiştiği zaman.
Date | string | number | null–
onChangePasswordSecuritySettings'tekiyle aynı.
(change) => Promise<void | errors>–
PropTürVarsayılan
emailOnayda ve kurtarma kodları dosyasında adı geçer.
string–
enabledİki aşamalı doğrulamanın açık olup olmadığı.
boolean–
onStartonStartTwoFactor ile aynı.
() => Promise<{ uri, secret?, recoveryCodes? }>–
onVerifyonVerifyTwoFactor ile aynı.
(code: string) => Promise<void | string[]>–
onDisableonDisableTwoFactor ile aynı.
() => Promise<void>–

SecurityPasskeys

Ayrıca her SettingsGroup prop'unu kabul eder.

PropTürVarsayılan
passkeysKayıtlı passkey'ler.
SecurityPasskey[]–
onAddWebAuthn kaydınızı çalıştırır.
() => Promise<void>–
onRemoveBir passkey'i kaldırır.
(id: string) => Promise<void>–
supportedBu tarayıcının passkey oluşturup oluşturamayacağı.
booleandetected

SecurityDeleteAccount

Ayrıca her SettingsGroup prop'unu kabul eder.

PropTürVarsayılan
emailOnay ifadesi delete ve ardından bu değerdir.
string–
onDeleteHesabı siler.
() => Promise<void>–
consequencesOnunla birlikte silinenler.
ReactNode[]–
titleGrup başlığı.
ReactNode"Danger zone"
TuşAction
TabSatırlar ve düğmeleri arasında, ardından her iletişim kutusunda gezinir.
EnterParola formunu herhangi bir alandan gönderir ve ifade eşleşince hesabı ifade alanından siler.
EscapeBir şey kaydedilirken hariç, bir iletişim kutusunu kapatır.
0–9Doğrulama kodunu doldurur. Altı rakam da girilince kendini denetler ve kod yapıştırmak da çalışır.
  • Yıkıcı eylemler asla ilk odağı almaz. Onaylar, oturumu kapatılan cihazlar ya da silinen e-posta gibi etkiledikleri şeyi adlandırır ve düğme yerine silme ifadesi alanı odak alır.
  • Bir satır katlandığında odak sonraki ya da önceki satırın düğmesine geçer; böylece sayfaya asla düşmez. Bir iletişim kutusunu kapatmak odağı onu açan düğmeye, düğme yoksa yerine geçene döndürür.
  • Oturum kapatmalar, eklenen ve kaldırılan passkey'ler ve parola gücü polite olarak duyurulur. Callback'lerinizden gelen hatalar alert olarak duyurulur ve ait oldukları alanın ya da düğmenin yanında kalır.
  • Her parola gereksinimi karşılanıp karşılanmadığını söyler, QR kodunun bir metin etiketi vardır ve kurulum anahtarı ile kurtarma kodları seçebileceğiniz okunabilir metindir.
  • Azaltılmış harekette satırlar katlanmak yerine solar ve adım değişiklikleri kaymaz. Yüksek karşıtlık modunda QR kodu ve güç ölçeri şekillerini korur.

Şunlarla geliştirildi

Security bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.

Kod

12 dosya, components/blocks/security konumuna eklendi.