unified code verification service
This commit is contained in:
@@ -1,10 +1,105 @@
|
||||
package verificationimp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type TokenHasher interface {
|
||||
Hash(raw string, token *model.VerificationToken) string
|
||||
}
|
||||
|
||||
type magicHasher struct{}
|
||||
|
||||
func (magicHasher) Hash(raw string, _ *model.VerificationToken) string {
|
||||
return tokenHash(raw)
|
||||
}
|
||||
|
||||
type otpHasher struct{}
|
||||
|
||||
func (otpHasher) Hash(raw string, t *model.VerificationToken) string {
|
||||
return otpHash(raw, *t.Salt)
|
||||
}
|
||||
|
||||
func hasherFor(t *model.VerificationToken) TokenHasher {
|
||||
if t.Salt != nil {
|
||||
return otpHasher{}
|
||||
}
|
||||
return magicHasher{}
|
||||
}
|
||||
|
||||
const verificationTokenBytes = 32
|
||||
const otpDigits = 6
|
||||
|
||||
func generateMagic() (raw, hash string, err error) {
|
||||
rawBytes := make([]byte, verificationTokenBytes)
|
||||
if _, err = rand.Read(rawBytes); err != nil {
|
||||
return
|
||||
}
|
||||
raw = base64.RawURLEncoding.EncodeToString(rawBytes)
|
||||
hash = tokenHash(raw)
|
||||
return
|
||||
}
|
||||
|
||||
func generateDeterministicMagic(seed string) (raw, hash string) {
|
||||
sum := sha256.Sum256([]byte("magic:" + seed))
|
||||
raw = base64.RawURLEncoding.EncodeToString(sum[:])
|
||||
hash = tokenHash(raw)
|
||||
return
|
||||
}
|
||||
|
||||
func generateOTP() (code, salt, hash string, err error) {
|
||||
// otpDigits-digit code
|
||||
n := make([]byte, 4)
|
||||
if _, err = rand.Read(n); err != nil {
|
||||
return
|
||||
}
|
||||
num := int(n[0])<<24 | int(n[1])<<16 | int(n[2])<<8 | int(n[3])
|
||||
|
||||
mod := 1
|
||||
for i := 0; i < otpDigits; i++ {
|
||||
mod *= 10
|
||||
}
|
||||
|
||||
code = fmt.Sprintf("%0*d", otpDigits, num%mod)
|
||||
|
||||
// per-token salt
|
||||
saltBytes := make([]byte, 16)
|
||||
if _, err = rand.Read(saltBytes); err != nil {
|
||||
return
|
||||
}
|
||||
salt = base64.RawURLEncoding.EncodeToString(saltBytes)
|
||||
|
||||
hash = otpHash(code, salt)
|
||||
return
|
||||
}
|
||||
|
||||
func generateDeterministicOTP(seed string) (code, salt, hash string) {
|
||||
sum := sha256.Sum256([]byte("otp:" + seed))
|
||||
|
||||
num := int(sum[0])<<24 | int(sum[1])<<16 | int(sum[2])<<8 | int(sum[3])
|
||||
mod := 1
|
||||
for i := 0; i < otpDigits; i++ {
|
||||
mod *= 10
|
||||
}
|
||||
code = fmt.Sprintf("%0*d", otpDigits, num%mod)
|
||||
|
||||
salt = base64.RawURLEncoding.EncodeToString(sum[4:20])
|
||||
hash = otpHash(code, salt)
|
||||
return
|
||||
}
|
||||
|
||||
// We store only the resulting hash (+salt) in DB, never the OTP itself.
|
||||
func otpHash(code, salt string) string {
|
||||
sum := sha256.Sum256([]byte(salt + ":" + code))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func tokenHash(rawToken string) string {
|
||||
hash := sha256.Sum256([]byte(rawToken))
|
||||
return base64.RawURLEncoding.EncodeToString(hash[:])
|
||||
|
||||
Reference in New Issue
Block a user