Skip to Content
SDK@solidus-network/auth-otp

@solidus-network/auth-otp

Saf bir OTP (tek kullanımlık kod) giriş çekirdeği. 6 haneli kodlar üretir, saklar ve doğrular, ardından teslimatı ve kimlik çözümlemesini iki küçük arayüz aracılığıyla size geri devreder.

Sıfır çalışma zamanı bağımlılığı vardır ve tasarım gereği tek başına hiçbir işe yaramaz. Kendi göndericinizi ve kendi oturum düzenleyicinizi siz getirirsiniz.

npm install @solidus-network/auth-otp
import { startLogin, completeLogin, memoryOtpStore } from '@solidus-network/auth-otp'

Kasıtlı olarak yapmadığı şeyler

Buna göre tasarım yapmadan önce bunu okuyun — bunlar sonraki bir yamada doldurulacak boşluklar değil, reddedişlerdir.

It does notBecause
Herhangi bir şey göndermez. Hiçbir zaman Twilio, Vonage, Resend veya SES içe aktarımı yoktur.Teslimat makbuzları, gönderici kimlikleri ve bölgesel yönlendirme sizin bağdaştırıcınıza aittir. OtpSender’ı siz uygularsınız.
Kimliği çözümlemez veya basmaz.İlk kez görülen bir telefon numarası için bir did:solidus basmak, bir anahtar çifti üretmek ve gözetim altında tutmak anlamına gelir. Bu paket, bu kararı sizin adınıza vermeyi reddeder — resolvePrincipal’i siz uygularsınız.
Bir oturum düzenlemez. completeLogin, { did, isNew }’de durur.Kendi oturum token’ınızı, arka ucunuzun zaten sahip olduğu imzalayıcıyla imzalayın.

0.1.0’da hiçbir SMS göndericisi gönderilmez. channel parametresi 'sms'’i kabul eder ve çekirdek kanaldan bağımsızdır — ancak bu pakette gerçek bir SMS bağdaştırıcısı yoktur. E-posta kanalı, bugün bağladığınız herhangi bir OtpSender üzerinden tamamen kullanılabilir. Üretim düzeyinde bir SMS bağdaştırıcısı bir sağlayıcı hesabı ve bir test cihazı gerektirir, dolayısıyla bu eksik bir içe aktarım değil, ayrı bir iş parçasıdır.


startLogin

Bir kod üretir, saklar ve göndericinize teslim eder. Hiçbir şey döndürmez — kod, hiçbir zaman bu fonksiyonun dokunduğu dönüş değerinin veya herhangi bir günlüğün parçası olmaz.

startLogin( deps: { store: OtpStore sender: OtpSender now: number rng?: () => number expiresInMs?: number }, args: { channel: 'sms' | 'email'; to: string } ): Promise<void>

Parametreler

NameTypeDefaultDescription
deps.storeOtpStoreHash’lenmiş kodun tutulduğu yer
deps.senderOtpSenderTeslimat bağdaştırıcınız
deps.nownumberEnjekte edilmiş geçerli an — paket asla Date.now() çağırmaz
deps.rng() => numberMath.random[0, 1) aralığında bir float döndürmelidir. Üretimde bir CSPRNG enjekte edin
deps.expiresInMsnumberKod ömrü
args.channel'sms' | 'email'Göndericinize olduğu gibi iletilir
args.tostringTelefon numarası veya e-posta — aynı zamanda depo anahtarı

sender.send tarafından fırlatılan bir hata, bir ret olarak yayılır.

Örnek

import { startLogin, memoryOtpStore } from '@solidus-network/auth-otp' import { webcrypto } from 'node:crypto' const store = memoryOtpStore() // Your delivery adapter — the only place a real code appears. const sender = { async send(channel: 'sms' | 'email', to: string, code: string) { await resend.emails.send({ from: '[email protected]', to, subject: 'Your code', text: `Your code is ${code}. It expires in 5 minutes.`, }) }, } // A CSPRNG, not Math.random. const rng = () => webcrypto.getRandomValues(new Uint32Array(1))[0] / 2 ** 32 await startLogin( { store, sender, now: Date.now(), rng, expiresInMs: 5 * 60_000 }, { channel: 'email', to: '[email protected]' }, )

completeLogin

Kodu doğrular ve — yalnızca başarı durumundato’nun arkasındaki DID’i bulmak veya basmak için resolvePrincipal’inizi çağırır.

completeLogin( deps: { store: OtpStore resolvePrincipal: (to: string) => Promise<{ did: string; isNew: boolean }> now: number maxAttempts?: number }, args: { to: string; code: string } ): Promise<{ did: string; isNew: boolean } | { error: 'expired' | 'wrong' | 'locked' | 'none' }>

Sonucu error kontrolüyle ayırt edin — bir ok bayrağı yoktur.

Örnek

const result = await completeLogin( { store, now: Date.now(), maxAttempts: 5, async resolvePrincipal(to) { const existing = await db.users.findByEmail(to) if (existing) return { did: existing.did, isNew: false } const did = await mintDidWithYourOwnCustodyModel(to) return { did, isNew: true } }, }, { to: '[email protected]', code: '481920' }, ) if ('error' in result) { return reply.code(401).send({ error: result.error }) } // auth-otp stops here. Signing the session is your job. const token = await signSession({ sub: result.did })

verifyOtp

completeLogin kullanmıyorsanız, daha düşük seviyeli kontrol.

verifyOtp( store: OtpStore, args: { to: string; code: string; now: number; maxAttempts: number } ): Promise<VerifyOtpResult>

VerifyOtpResult, { ok: true } veya { ok: false; reason: 'expired' | 'wrong' | 'locked' | 'none' }’dir.

SituationResultRecord after
to için kayıt yokreason: 'none'
exp’i geçmişreason: 'expired'Tüketildi — süresi dolmuş bir kod yeniden denenemez
Doğru kodok: trueTüketildi — tek kullanımlık
Yanlış, hâlâ maxAttempts altındareason: 'wrong'Kalıcı olur, attempts + 1
Yanlış, maxAttempts’te veya üzerindereason: 'locked'Tüketildi — doğru kodla bile başka yeniden deneme yok

Son kullanma, startLogin’e ilettiğiniz aynı saat alanında (clock domain), enjekte edilmiş now’a karşı değerlendirilir.


Depolama

interface OtpStore { put(to: string, record: OtpRecord): Promise<void> get(to: string): Promise<OtpRecord | undefined> del(to: string): Promise<void> } interface OtpRecord { codeHash: string // SHA-256 hex — never the plaintext code exp: number // absolute expiry instant attempts: number // failed verifications so far }

memoryOtpStore(), Map destekli bir referans uygulamasıdır. Süreçler arasında güvenli değildir — çok örnekli bir dağıtım, aynı üç yöntemin arkasında Redis, Postgres veya eşdeğerine ihtiyaç duyar.

hashOtp hakkında — hash’lemenin size sağladığı ve sağlamadığı şey

Kodlar, tuzlanmamış (unsalted) bir SHA-256 özeti olarak saklanır. Bunun değeri konusunda kesin olmak gerekirse:

  • Kodları bir DB tarayıcısında, bir destek talebi ekran görüntüsünde, depoyu alan bir günlük toplayıcıda veya bir yedeklemede düz görünürlükten uzak tutar — yaygın “düz metin olarak orada duruyordu” sızıntısı.
  • Çevrimdışı kaba kuvvete karşı dayanıklı değildir. 6 haneli bir kodun yalnızca 1.000.000 olası değeri vardır; bir milyon hash’in tamamı bir saniyenin çok altında hesaplanır. Bir depo dökümüne sahip bir saldırgan, tüm canlı kodları kurtarır.

Çevrimiçi bir tahmin saldırısını gerçekte sınırlayan şey, hash değil, verifyOtp’nin maxAttempts kilitlenmesidir.


Test Edilebilirlik

Zamana ve rastgeleliğe bağlı her girdi enjekte edilir — çekirdekte hiçbir yerde Date.now() veya Math.random() yoktur. Sabit bir now ve deterministik bir rng iletin, tüm giriş akışı sahte zamanlayıcılar olmadan tam olarak yeniden üretilebilir hâle gelir.

const codes = ['481920'] let i = 0 const rng = () => Number(codes[i++]) / 1_000_000 await startLogin({ store, sender, now: 1_700_000_000_000, rng }, { channel: 'email', to: '[email protected]' })

Sonraki adımlar

  • @solidus-network/auth — zaten bir DID’e sahip kullanıcılar için DID meydan okuma-yanıt
  • @solidus-network/sdk — bir OTP girişinin bir DID bastığı kimlik bilgilerini düzenleyin ve doğrulayın
Last updated on