package authn import ( "crypto/rand" "crypto/sha256" "encoding/base32" "encoding/base64" "fmt" "strings" "time" "drive.local/drivev2/internal/application" ) type CredentialGenerator struct{} func (CredentialGenerator) NewCredential() (application.GeneratedCredential, error) { secret := make([]byte, 32) if _, err := rand.Read(secret); err != nil { return application.GeneratedCredential{}, fmt.Errorf("generate credential: %w", err) } plaintext := base64.RawURLEncoding.EncodeToString(secret) digest := sha256.Sum256([]byte(plaintext)) return application.GeneratedCredential{Plaintext: plaintext, Digest: digest[:]}, nil } func (CredentialGenerator) NewRecoveryCodes(count int) ([]application.GeneratedCredential, error) { codes := make([]application.GeneratedCredential, 0, count) for range count { raw := make([]byte, 12) if _, err := rand.Read(raw); err != nil { return nil, fmt.Errorf("generate recovery code: %w", err) } encoded := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(raw) plaintext := strings.Join([]string{encoded[0:5], encoded[5:10], encoded[10:15], encoded[15:20]}, "-") digest := sha256.Sum256([]byte(plaintext)) codes = append(codes, application.GeneratedCredential{Plaintext: plaintext, Digest: digest[:]}) } return codes, nil } type SystemClock struct{} func (SystemClock) Now() time.Time { return time.Now() }