Skip to content

Commit

Permalink
feat: passlink authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
loeffert committed May 24, 2024
1 parent 4f2ae80 commit 3f52834
Show file tree
Hide file tree
Showing 20 changed files with 924 additions and 9 deletions.
51 changes: 48 additions & 3 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"net/url"
"strings"
"time"

Expand All @@ -15,6 +16,7 @@ import (
"github.com/knadh/koanf/providers/file"
zeroLogger "github.com/rs/zerolog/log"
"github.com/teamhanko/hanko/backend/ee/saml/config"
"github.com/teamhanko/hanko/backend/persistence/models"
"golang.org/x/exp/slices"
)

Expand All @@ -25,6 +27,7 @@ type Config struct {
Smtp SMTP `yaml:"smtp" json:"smtp,omitempty" koanf:"smtp"`
EmailDelivery EmailDelivery `yaml:"email_delivery" json:"email_delivery,omitempty" koanf:"email_delivery" split_words:"true"`
Passcode Passcode `yaml:"passcode" json:"passcode" koanf:"passcode"`
Passlink Passlink `yaml:"passlink" json:"passlink,omitempty" koanf:"passlink"`
Password Password `yaml:"password" json:"password,omitempty" koanf:"password"`
Database Database `yaml:"database" json:"database" koanf:"database"`
Secrets Secrets `yaml:"secrets" json:"secrets" koanf:"secrets"`
Expand Down Expand Up @@ -135,6 +138,16 @@ func DefaultConfig() *Config {
Password: Password{
MinPasswordLength: 8,
},
Passlink: Passlink{
Enabled: false,
URL: "http://localhost:8888",
TTL: 300, // 5 minutes
Strictness: models.PasslinkStrictnessNone,
Email: Email{
FromAddress: "[email protected]",
FromName: "Hanko",
},
},
Database: Database{
Database: "hanko",
},
Expand Down Expand Up @@ -167,6 +180,10 @@ func DefaultConfig() *Config {
Tokens: 3,
Interval: 1 * time.Minute,
},
PasslinkLimits: RateLimits{
Tokens: 3,
Interval: 1 * time.Minute,
},
TokenLimits: RateLimits{
Tokens: 3,
Interval: 1 * time.Minute,
Expand Down Expand Up @@ -419,9 +436,36 @@ func (p *Passcode) Validate() error {
return nil
}

type Passlink struct {
Enabled bool `yaml:"enabled" json:"enabled,omitempty" koanf:"enabled" jsonschema:"default=false"`
URL string `yaml:"url" json:"url,omitempty" koanf:"url"`
TTL int `yaml:"ttl" json:"ttl,omitempty" koanf:"ttl" jsonschema:"default=300"`
Email Email `yaml:"email" json:"email,omitempty" koanf:"email"`
Strictness models.PasslinkStrictness `yaml:"strictness" json:"strictness,omitempty" koanf:"strictness" jsonschema:"default=none,enum=browser,enum=device,enum=none"`
}

func (p *Passlink) Validate() error {
err := p.Email.Validate()
if err != nil {
return fmt.Errorf("failed to validate email settings: %w", err)
}
if len(strings.TrimSpace(p.URL)) == 0 {
return errors.New("url must not be empty")
}
if url, err := url.Parse(p.URL); err != nil {
return fmt.Errorf("failed to parse url: %w", err)
} else if url.Scheme == "" || url.Host == "" {
return errors.New("url must be a valid URL")
}
if !p.Strictness.Valid() {
return fmt.Errorf("invalid passlink strictness: %s", p.Strictness)
}
return nil
}

// Database connection settings
type Database struct {
Database string `yaml:"database" json:"database,omitempty" koanf:"database" jsonschema:"default=hanko" jsonschema:"oneof_required=config"`
Database string `yaml:"database" json:"database,omitempty" koanf:"database" jsonschema:"oneof_required=config,default=hanko"`
User string `yaml:"user" json:"user,omitempty" koanf:"user" jsonschema:"oneof_required=config"`
Password string `yaml:"password" json:"password,omitempty" koanf:"password" jsonschema:"oneof_required=config"`
Host string `yaml:"host" json:"host,omitempty" koanf:"host" jsonschema:"oneof_required=config"`
Expand Down Expand Up @@ -527,6 +571,7 @@ type RateLimiter struct {
Redis *RedisConfig `yaml:"redis_config" json:"redis_config,omitempty" koanf:"redis_config"`
PasscodeLimits RateLimits `yaml:"passcode_limits" json:"passcode_limits,omitempty" koanf:"passcode_limits" split_words:"true"`
PasswordLimits RateLimits `yaml:"password_limits" json:"password_limits,omitempty" koanf:"password_limits" split_words:"true"`
PasslinkLimits RateLimits `yaml:"passlink_limits" json:"passlink_limits,omitempty" koanf:"passlink_limits" split_words:"true"`
TokenLimits RateLimits `yaml:"token_limits" json:"token_limits,omitempty" koanf:"token_limits" split_words:"true"`
}

Expand All @@ -539,7 +584,7 @@ type RateLimiterStoreType string

const (
RATE_LIMITER_STORE_IN_MEMORY RateLimiterStoreType = "in_memory"
RATE_LIMITER_STORE_REDIS = "redis"
RATE_LIMITER_STORE_REDIS RateLimiterStoreType = "redis"
)

func (r *RateLimiter) Validate() error {
Expand Down Expand Up @@ -673,7 +718,7 @@ func (p *ThirdPartyProviders) HasEnabled() bool {
func (p *ThirdPartyProviders) Get(provider string) *ThirdPartyProvider {
s := structs.New(p)
for _, field := range s.Fields() {
if strings.ToLower(field.Name()) == strings.ToLower(provider) {
if strings.EqualFold(field.Name(), provider) {
p := field.Value().(ThirdPartyProvider)
return &p
}
Expand Down
28 changes: 28 additions & 0 deletions backend/crypto/passlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package crypto

import (
"crypto/rand"
"encoding/hex"
"log"
)

// PasslinkGenerator will generate a random passlink token
type PasslinkGenerator interface {
Generate() (string, error)
}

type passlinkGenerator struct {
}

func NewPasslinkGenerator() PasslinkGenerator {
return &passlinkGenerator{}
}

func (g *passlinkGenerator) Generate() (string, error) {
bytes := make([]byte, 32)
_, err := rand.Read(bytes)
if err != nil {
log.Fatal(err)
}
return hex.EncodeToString(bytes), nil
}
2 changes: 2 additions & 0 deletions backend/dto/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
// PublicConfig is the part of the configuration that will be shared with the frontend
type PublicConfig struct {
Password config.Password `json:"password"`
Passlink bool `json:"passlink"`
Emails config.Emails `json:"emails"`
Providers []string `json:"providers"`
Account config.Account `json:"account"`
Expand All @@ -19,6 +20,7 @@ type PublicConfig struct {
func FromConfig(config config.Config) PublicConfig {
return PublicConfig{
Password: config.Password,
Passlink: config.Passlink.Enabled,
Emails: config.Emails,
Providers: GetEnabledProviders(config.ThirdParty.Providers),
Account: config.Account,
Expand Down
22 changes: 22 additions & 0 deletions backend/dto/passlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dto

import (
"time"
)

type PasslinkFinishRequest struct {
ID string `json:"id" validate:"required,uuid4"`
Token string `json:"token" validate:"required"`
}

type PasslinkInitRequest struct {
UserID string `json:"user_id" validate:"required,uuid4"`
EmailID *string `json:"email_id"`
RedirectPath string `json:"redirect_path" validate:"required"`
}

type PasslinkReturn struct {
ID string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UserID string `json:"user_id"`
}
14 changes: 14 additions & 0 deletions backend/dto/webhook/email.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package webhook

import "github.com/teamhanko/hanko/backend/persistence/models"

type EmailSend struct {
Subject string `json:"subject"` // subject
BodyPlain string `json:"body_plain"` // used for string templates
Expand All @@ -19,8 +21,20 @@ type PasscodeData struct {
ValidUntil int64 `json:"valid_until"` // UnixTimestamp
}

type PasslinkData struct {
ServiceName string `json:"service_name"`
Token string `json:"token"`
URL string `json:"url"`
TTL int `json:"ttl"`
ValidUntil int64 `json:"valid_until"` // UnixTimestamp
RedirectPath string `json:"redirect_path"`
RetryLimit int `json:"retry_limit"`
Strictness models.PasslinkStrictness `json:"strictness"`
}

type EmailType string

var (
EmailTypePasscode EmailType = "passcode"
EmailTypePasslink EmailType = "passlink"
)
4 changes: 3 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module github.com/teamhanko/hanko/backend

go 1.20
go 1.21

toolchain go1.22.3

require (
github.com/brianvoe/gofakeit/v6 v6.28.0
Expand Down
9 changes: 9 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ClickHouse/ch-go v0.55.0 h1:jw4Tpx887YXrkyL5DfgUome/po8MLz92nz2heOQ6RjQ=
github.com/ClickHouse/ch-go v0.55.0/go.mod h1:kQT2f+yp2p+sagQA/7kS6G3ukym+GQ5KAu1kuFAFDiU=
github.com/ClickHouse/clickhouse-go/v2 v2.9.1 h1:IeE2bwVvAba7Yw5ZKu98bKI4NpDmykEy6jUaQdJJCk8=
Expand Down Expand Up @@ -76,12 +77,14 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
Expand Down Expand Up @@ -122,6 +125,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
Expand Down Expand Up @@ -184,7 +188,9 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -217,6 +223,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -367,6 +374,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
Expand Down Expand Up @@ -855,6 +863,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
Expand Down
Loading

0 comments on commit 3f52834

Please sign in to comment.