107 lines
2.3 KiB
Go
107 lines
2.3 KiB
Go
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[:])
|
|
}
|