✨ Impl for totp code, and in app notify factor
This commit is contained in:
parent
dd9a44d126
commit
123342b13b
2
go.mod
2
go.mod
@ -32,6 +32,7 @@ require (
|
|||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
@ -64,6 +65,7 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
|
5
go.sum
5
go.sum
@ -51,6 +51,9 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
|
github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
|
||||||
|
github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@ -271,6 +274,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
|
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
@ -8,7 +8,10 @@ import (
|
|||||||
"git.solsynth.dev/hypernet/passport/pkg/internal/http/exts"
|
"git.solsynth.dev/hypernet/passport/pkg/internal/http/exts"
|
||||||
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
"git.solsynth.dev/hypernet/passport/pkg/internal/services"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getAvailableFactors(c *fiber.Ctx) error {
|
func getAvailableFactors(c *fiber.Ctx) error {
|
||||||
@ -100,10 +103,45 @@ func createFactor(c *fiber.Ctx) error {
|
|||||||
Account: user,
|
Account: user,
|
||||||
AccountID: user.ID,
|
AccountID: user.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additionalOnceConfig := map[string]any{}
|
||||||
|
|
||||||
|
switch data.Type {
|
||||||
|
case models.TimeOtpFactor:
|
||||||
|
cfg := totp.GenerateOpts{
|
||||||
|
Issuer: viper.GetString("name"),
|
||||||
|
AccountName: user.Name,
|
||||||
|
Period: 30,
|
||||||
|
SecretSize: 20,
|
||||||
|
Digits: 6,
|
||||||
|
}
|
||||||
|
key, err := totp.Generate(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate totp key: %v", err)
|
||||||
|
}
|
||||||
|
factor.Secret = key.Secret()
|
||||||
|
factor.Config = datatypes.NewJSONType(map[string]any{
|
||||||
|
"issuer": cfg.Issuer,
|
||||||
|
"account_name": cfg.AccountName,
|
||||||
|
"period": cfg.Period,
|
||||||
|
"secret_size": cfg.SecretSize,
|
||||||
|
"digits": cfg.Digits,
|
||||||
|
})
|
||||||
|
additionalOnceConfig["url"] = key.URL()
|
||||||
|
}
|
||||||
|
|
||||||
if err := database.C.Create(&factor).Error; err != nil {
|
if err := database.C.Create(&factor).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(additionalOnceConfig) > 0 {
|
||||||
|
data := factor.Config.Data()
|
||||||
|
for k, v := range additionalOnceConfig {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
factor.Config = datatypes.NewJSONType(data)
|
||||||
|
}
|
||||||
|
|
||||||
return c.JSON(factor)
|
return c.JSON(factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"git.solsynth.dev/hypernet/pusher/pkg/pushkit"
|
"git.solsynth.dev/hypernet/pusher/pkg/pushkit"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -70,6 +71,36 @@ func CountUserFactor(userId uint) int64 {
|
|||||||
|
|
||||||
func GetFactorCode(factor models.AuthFactor) (bool, error) {
|
func GetFactorCode(factor models.AuthFactor) (bool, error) {
|
||||||
switch factor.Type {
|
switch factor.Type {
|
||||||
|
case models.InAppNotifyFactor:
|
||||||
|
var user models.Account
|
||||||
|
if err := database.C.Where(&models.Account{
|
||||||
|
BaseModel: models.BaseModel{ID: factor.AccountID},
|
||||||
|
}).First(&user).Error; err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := uuid.NewString()[:6]
|
||||||
|
|
||||||
|
identifier := fmt.Sprintf("%s%d", gap.FactorOtpPrefix, factor.ID)
|
||||||
|
_, err := gap.Jt.Publish(identifier, []byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
return true, fmt.Errorf("error during publish message: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info().Uint("factor", factor.ID).Str("secret", secret).Msg("Published one-time-password to JetStream...")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = PushNotification(models.Notification{
|
||||||
|
Topic: "passport.security.otp",
|
||||||
|
Title: "Your login one-time-password",
|
||||||
|
Body: fmt.Sprintf("`%s` is your login verification code. It will expires in 30 minutes.", secret),
|
||||||
|
Account: user,
|
||||||
|
AccountID: user.ID,
|
||||||
|
}, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Uint("factor", factor.ID).Msg("Failed to delivery one-time-password via notify...")
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
case models.EmailPasswordFactor:
|
case models.EmailPasswordFactor:
|
||||||
var user models.Account
|
var user models.Account
|
||||||
if err := database.C.Where(&models.Account{
|
if err := database.C.Where(&models.Account{
|
||||||
@ -103,7 +134,6 @@ func GetFactorCode(factor models.AuthFactor) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -117,6 +147,13 @@ func CheckFactor(factor models.AuthFactor, code string) error {
|
|||||||
nil,
|
nil,
|
||||||
fmt.Errorf("invalid password"),
|
fmt.Errorf("invalid password"),
|
||||||
)
|
)
|
||||||
|
case models.TimeOtpFactor:
|
||||||
|
lo.Ternary(
|
||||||
|
totp.Validate(code, factor.Secret),
|
||||||
|
nil,
|
||||||
|
fmt.Errorf("invalid verification code"),
|
||||||
|
)
|
||||||
|
case models.InAppNotifyFactor:
|
||||||
case models.EmailPasswordFactor:
|
case models.EmailPasswordFactor:
|
||||||
identifier := fmt.Sprintf("%s%d", gap.FactorOtpPrefix, factor.ID)
|
identifier := fmt.Sprintf("%s%d", gap.FactorOtpPrefix, factor.ID)
|
||||||
sub, err := gap.Jt.PullSubscribe(identifier, "otp_validator", nats.BindStream("OTPs"))
|
sub, err := gap.Jt.PullSubscribe(identifier, "otp_validator", nats.BindStream("OTPs"))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user