1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-12-20 12:51:17 +00:00

[#25] Make TOTP provider fully configurable (#29)

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-12-29 00:38:56 +01:00 committed by GitHub
parent f1fe845bc8
commit 20bb1b4745
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 65 deletions

View file

@ -44,6 +44,7 @@ mfa:
secret_key: "foobar"
duo:
# Get your ikey / skey / host from https://duo.com/docs/duoweb#first-steps
ikey: "IKEY"
skey: "SKEY"
host: "HOST"
@ -103,9 +104,15 @@ providers:
mfa:
luzifer:
- provider: duo
- provider: google
- provider: totp
attributes:
secret: MZXW6YTBOIFA
secret: MZXW6YTBOIFA # required
period: 30 # optional, defaults to 30 (Google Authenticator)
skew: 1 # optional, defaults to 1 (Google Authenticator)
digits: 8 # optional, defaults to 6 (Google Authenticator)
algorithm: sha1 # optional (sha1, sha256, sha512), defaults to sha1 (Google Authenticator)
- provider: yubikey
attributes:
device: ccccccfcvuul

10
mfa.go
View file

@ -26,6 +26,16 @@ func newMFAConfig(provider string, attrs map[string]interface{}) mfaConfig {
return mfaConfig{Provider: provider, Attributes: attrs}
}
func (m mfaConfig) AttributeInt(key string) int {
if v, ok := m.Attributes[key]; ok && v != "" {
if sv, ok := v.(int); ok {
return sv
}
}
return 0
}
func (m mfaConfig) AttributeString(key string) string {
if v, ok := m.Attributes[key]; ok {
if sv, ok := v.(string); ok {

View file

@ -1,63 +0,0 @@
package main
import (
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/pquerna/otp/totp"
)
func init() {
registerMFAProvider(&mfaGoogle{})
}
type mfaGoogle struct{}
// ProviderID needs to return an unique string to identify
// this special MFA provider
func (m mfaGoogle) ProviderID() (id string) {
return "google"
}
// Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function
// needs to return the errProviderUnconfigured
func (m mfaGoogle) Configure(yamlSource []byte) (err error) { return nil }
// ValidateMFA takes the user from the login cookie and performs a
// validation against the provided MFA configuration for this user
func (m mfaGoogle) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
// Look for mfaConfigs with own provider name
for _, c := range mfaCfgs {
if c.Provider != m.ProviderID() {
continue
}
token, err := m.exec(c)
if err != nil {
return errors.Wrap(err, "Generating the MFA token failed")
}
for key, values := range r.Form {
if strings.HasSuffix(key, mfaLoginFieldName) && values[0] == token {
return nil
}
}
}
// Report this provider was not able to verify the MFA request
return errNoValidUserFound
}
func (m mfaGoogle) exec(c mfaConfig) (string, error) {
secret := c.AttributeString("secret")
if n := len(secret) % 8; n != 0 {
secret = secret + strings.Repeat("=", 8-n)
}
return totp.GenerateCode(strings.ToUpper(secret), time.Now())
}

98
mfa_totp.go Normal file
View file

@ -0,0 +1,98 @@
package main
import (
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
func init() {
registerMFAProvider(&mfaTOTP{})
}
type mfaTOTP struct{}
// ProviderID needs to return an unique string to identify
// this special MFA provider
func (m mfaTOTP) ProviderID() (id string) {
return "totp"
}
// Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function
// needs to return the errProviderUnconfigured
func (m mfaTOTP) Configure(yamlSource []byte) (err error) { return nil }
// ValidateMFA takes the user from the login cookie and performs a
// validation against the provided MFA configuration for this user
func (m mfaTOTP) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
// Look for mfaConfigs with own provider name
for _, c := range mfaCfgs {
// Provider has been renamed, keep "google" for backwards compatibility
if c.Provider != m.ProviderID() && c.Provider != "google" {
continue
}
token, err := m.exec(c)
if err != nil {
return errors.Wrap(err, "Generating the MFA token failed")
}
for key, values := range r.Form {
if strings.HasSuffix(key, mfaLoginFieldName) && values[0] == token {
return nil
}
}
}
// Report this provider was not able to verify the MFA request
return errNoValidUserFound
}
func (m mfaTOTP) exec(c mfaConfig) (string, error) {
secret := c.AttributeString("secret")
// By default use Google Authenticator compatible settings
generatorOpts := totp.ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
}
if period := c.AttributeInt("period"); period > 0 {
generatorOpts.Period = uint(period)
}
if skew := c.AttributeInt("skew"); skew > 0 {
generatorOpts.Skew = uint(skew)
}
if digits := c.AttributeInt("digits"); digits > 0 {
generatorOpts.Digits = otp.Digits(digits)
}
if algorithm := c.AttributeString("algorithm"); algorithm != "" {
switch algorithm {
case "sha1":
generatorOpts.Algorithm = otp.AlgorithmSHA1
case "sha256":
generatorOpts.Algorithm = otp.AlgorithmSHA256
case "sha512":
generatorOpts.Algorithm = otp.AlgorithmSHA512
default:
return "", errors.Errorf("Unsupported algorithm %q", algorithm)
}
}
if n := len(secret) % 8; n != 0 {
secret = secret + strings.Repeat("=", 8-n)
}
return totp.GenerateCodeCustom(strings.ToUpper(secret), time.Now(), generatorOpts)
}