Initial commit
This commit is contained in:
commit
036c1824a9
14 changed files with 1867 additions and 0 deletions
111
.gitignore
vendored
Normal file
111
.gitignore
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/wroofauth
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
43
cmd/root.go
Normal file
43
cmd/root.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "wroofauth",
|
||||
Short: "The auth system that might bite.",
|
||||
Long: `A longer description that spans multiple lines and likely contains
|
||||
examples and usage of using your application. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
|
||||
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.wroofauth.yaml)")
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
35
cmd/serve.go
Normal file
35
cmd/serve.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"git.1in9.net/raider/wroofauth/internal/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// serveCmd represents the serve command
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Starts WroofAuth in Server mode",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
server.Serve()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// serveCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// serveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
55
go.mod
Normal file
55
go.mod
Normal file
|
@ -0,0 +1,55 @@
|
|||
module git.1in9.net/raider/wroofauth
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/improbable-eng/grpc-web v0.15.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pquerna/otp v1.4.0 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/cobra-cli v1.3.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.17.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.1 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
nhooyr.io/websocket v1.8.6 // indirect
|
||||
)
|
50
internal/database/mongodb.go
Normal file
50
internal/database/mongodb.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.mongodb.org/mongo-driver/event"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
)
|
||||
|
||||
var dbClient *mongo.Client
|
||||
var MongoDatabase *mongo.Database
|
||||
|
||||
func Connect() {
|
||||
cmdMonitor := &event.CommandMonitor{
|
||||
Started: func(_ context.Context, evt *event.CommandStartedEvent) {
|
||||
// TODO: Log
|
||||
},
|
||||
}
|
||||
|
||||
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(viper.GetString("mongo.uri")).SetMonitor(cmdMonitor))
|
||||
if err != nil {
|
||||
// TODO: Log
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = client.Ping(context.TODO(), readpref.Nearest())
|
||||
if err != nil {
|
||||
// TODO: Log
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dbClient = client
|
||||
|
||||
MongoDatabase = client.Database(viper.GetString("mongo.database"))
|
||||
|
||||
//UserCollection = Database.Collection(viper.GetString("mongo.collection.users"))
|
||||
//ClientCollection = Database.Collection(viper.GetString("mongo.collection.clients"))
|
||||
//GroupCollection = Database.Collection(viper.GetString("mongo.collection.groups"))
|
||||
}
|
||||
|
||||
func Disconnect() {
|
||||
err := dbClient.Disconnect(context.TODO())
|
||||
if err != nil {
|
||||
// TODO: Log
|
||||
panic(err)
|
||||
}
|
||||
}
|
8
internal/entities/entity.go
Normal file
8
internal/entities/entity.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package entities
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
type Entity interface {
|
||||
GetType() string
|
||||
GetID() primitive.ObjectID
|
||||
}
|
1
internal/entities/user/db.go
Normal file
1
internal/entities/user/db.go
Normal file
|
@ -0,0 +1 @@
|
|||
package user
|
216
internal/entities/user/user.go
Normal file
216
internal/entities/user/user.go
Normal file
|
@ -0,0 +1,216 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"git.1in9.net/raider/wroofauth/internal/parameters"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
|
||||
ErrIncompatibleVersion = errors.New("incompatible version of argon2")
|
||||
ErrWrongPassword = errors.New("wrong password")
|
||||
ErrPasswordLoginDeactivated = errors.New("password login deactivated for this user")
|
||||
)
|
||||
|
||||
type UserSecondFactor struct {
|
||||
Name string `bson:"name"`
|
||||
Type string `bson:"type"`
|
||||
Enabled bool `bson:"enabled"`
|
||||
Arguments map[string]string `bson:"arguments"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID primitive.ObjectID `bson:"_id"`
|
||||
|
||||
Username *string `bson:"username,omitempty"`
|
||||
Email *string `bson:"email,omitempty"`
|
||||
PasswordHash *string `bson:"password,omitempty"`
|
||||
PasswordChangeTimestamp time.Time `bson:"passwordChangeTimestamp,omitempty"`
|
||||
|
||||
SecondFactors []*UserSecondFactor `bson:"secondFactors"`
|
||||
SecondFactorOverride bool `bson:"secondFactorOverride,omitempty"` // Can be used to temporarily disable 2fa
|
||||
}
|
||||
|
||||
func (u *User) GetType() string {
|
||||
return "user"
|
||||
}
|
||||
|
||||
func (u *User) GetID() primitive.ObjectID {
|
||||
return u.ID
|
||||
}
|
||||
|
||||
func (u *User) GetFriendlyIdentifier() string {
|
||||
if u.Username != nil {
|
||||
return *u.Username
|
||||
}
|
||||
|
||||
if u.Email != nil {
|
||||
return *u.Email
|
||||
}
|
||||
|
||||
return u.ID.Hex() // User with no identification, fall back to just echoing the ID
|
||||
}
|
||||
|
||||
func (u *User) countActive2FA() int {
|
||||
count := 0
|
||||
|
||||
for _, factor := range u.SecondFactors {
|
||||
if !factor.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (u *User) Needs2FA() bool {
|
||||
return !u.SecondFactorOverride && u.countActive2FA() > 0
|
||||
}
|
||||
|
||||
func (u *User) CheckPassword(password string) error {
|
||||
if u.PasswordHash == nil {
|
||||
return ErrPasswordLoginDeactivated
|
||||
}
|
||||
|
||||
p, salt, hash, err := decodeHash(*u.PasswordHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherHash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
|
||||
|
||||
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
|
||||
return nil
|
||||
}
|
||||
return ErrWrongPassword
|
||||
}
|
||||
|
||||
func (u *User) SetPassword(password string) error {
|
||||
p := parameters.GetPasswordParams()
|
||||
|
||||
salt, err := parameters.GenerateRandomBytes(p.SaltLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash := argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength)
|
||||
|
||||
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.Memory, p.Iterations, p.Parallelism, b64Salt, b64Hash)
|
||||
|
||||
u.PasswordHash = &encodedHash
|
||||
u.PasswordChangeTimestamp = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) AddTOTP(name string) (string, error) {
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: viper.GetString("totp.issuer"),
|
||||
AccountName: u.GetFriendlyIdentifier(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
u.SecondFactors = append(u.SecondFactors, &UserSecondFactor{
|
||||
Name: name,
|
||||
Type: "totp",
|
||||
Enabled: false,
|
||||
Arguments: map[string]string{
|
||||
"secret": key.Secret(),
|
||||
},
|
||||
})
|
||||
|
||||
return key.String(), nil
|
||||
}
|
||||
|
||||
func (u *User) EnableTOTP(code string) error {
|
||||
for _, factor := range u.SecondFactors {
|
||||
if factor.Enabled || factor.Type != "totp" {
|
||||
continue
|
||||
}
|
||||
|
||||
success := totp.Validate(code, factor.Arguments["secret"])
|
||||
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
|
||||
factor.Enabled = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("no such totp")
|
||||
}
|
||||
|
||||
func (u *User) ValidateTOTP(code string) error {
|
||||
for _, factor := range u.SecondFactors {
|
||||
if !factor.Enabled || factor.Type != "totp" {
|
||||
continue
|
||||
}
|
||||
|
||||
success := totp.Validate(code, factor.Arguments["secret"])
|
||||
|
||||
if !success {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("no such totp")
|
||||
}
|
||||
|
||||
func decodeHash(encodedHash string) (p *parameters.PasswordParams, salt, hash []byte, err error) {
|
||||
values := strings.Split(encodedHash, "$")
|
||||
if len(values) != 6 {
|
||||
return nil, nil, nil, ErrInvalidHash
|
||||
}
|
||||
|
||||
var version int
|
||||
_, err = fmt.Sscanf(values[2], "v=%d", &version)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if version != argon2.Version {
|
||||
return nil, nil, nil, ErrIncompatibleVersion
|
||||
}
|
||||
|
||||
p = ¶meters.PasswordParams{}
|
||||
_, err = fmt.Sscanf(values[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
salt, err = base64.RawStdEncoding.Strict().DecodeString(values[4])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.SaltLength = uint32(len(salt))
|
||||
|
||||
hash, err = base64.RawStdEncoding.Strict().DecodeString(values[5])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.KeyLength = uint32(len(hash))
|
||||
|
||||
return p, salt, hash, nil
|
||||
}
|
176
internal/machines/session.go
Normal file
176
internal/machines/session.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package machines
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.1in9.net/raider/wroofauth/internal/entities/user"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIllegalStateAction = errors.New("illegal action for this state")
|
||||
ErrIllegalState = errors.New("illegal state")
|
||||
)
|
||||
|
||||
type SessionState string
|
||||
|
||||
const (
|
||||
SessionState_EMPTY SessionState = "EMPTY"
|
||||
SessionState_UNAUTHENTICATED SessionState = "UNAUTHENTICATED"
|
||||
SessionState_AWAITING_FACTOR SessionState = "AWAITING_FACTOR"
|
||||
SessionState_AUTHENTICATED_PENDING SessionState = "AUTHENTICATED_PENDING"
|
||||
SessionState_AUTHENTICATED_FULLY SessionState = "AUTHENTICATED_FULLY"
|
||||
SessionState_AUTHENTICATED_PASSWORD_CHANGE SessionState = "AUTHENTICATED_PASSWORD_CHANGE"
|
||||
SessionState_AUTHENTICATED_2FA_ENROLL SessionState = "AUTHENTICATED_2FA_ENROLL"
|
||||
SessionState_AUTHENTICATED_REVIEW_TOS SessionState = "AUTHENTICATED_REVIEW_TOS"
|
||||
SessionState_AUTHENTICATED_REVIEW_RECOVERY SessionState = "AUTHENTICATED_REVIEW_RECOVERY"
|
||||
)
|
||||
|
||||
type AuthenticationMethod string
|
||||
|
||||
const (
|
||||
AuthenticationMethod_NONE = "NONE"
|
||||
AuthenticationMethod_PASSWORD = "PASSWORD"
|
||||
)
|
||||
|
||||
type SecondFactor string
|
||||
|
||||
const (
|
||||
SecondFactor_NONE = "NONE"
|
||||
SecondFactor_TOTP = "TOTP"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
State SessionState
|
||||
|
||||
AuthenticationMethod AuthenticationMethod
|
||||
SecondFactor SecondFactor
|
||||
|
||||
User *user.User
|
||||
}
|
||||
|
||||
func NewSession() *Session {
|
||||
return &Session{
|
||||
State: SessionState_EMPTY,
|
||||
AuthenticationMethod: AuthenticationMethod_NONE,
|
||||
SecondFactor: SecondFactor_NONE,
|
||||
}
|
||||
}
|
||||
|
||||
// s.Validate checks if the session is in a valid state
|
||||
func (s *Session) Validate() error {
|
||||
if s.IsAnyAuthenticated() {
|
||||
if s.User == nil {
|
||||
// We can only be here if a user is set
|
||||
return ErrIllegalState
|
||||
}
|
||||
|
||||
if s.AuthenticationMethod == AuthenticationMethod_NONE {
|
||||
// We can not be authenticated without any way we got here
|
||||
return ErrIllegalState
|
||||
}
|
||||
|
||||
if s.SecondFactor == SecondFactor_NONE && s.User.Needs2FA() {
|
||||
// User has either just enabled 2FA, or something went horribly wrong.
|
||||
// Either way, throw away the session!
|
||||
return ErrIllegalState
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) IsAnyAuthenticated() bool {
|
||||
return strings.HasPrefix(string(s.State), "AUTHENTICATED_")
|
||||
}
|
||||
|
||||
func (s *Session) performPreflight() error {
|
||||
// TODO: Do Preflight Checks.
|
||||
|
||||
// TODO: Do PASSWORD_EXPIRED check
|
||||
|
||||
// TODO: Do 2FA-Force-Enrolment check
|
||||
|
||||
// TODO: Do TOS check
|
||||
|
||||
// TODO: Do Reveiw Recovery check
|
||||
|
||||
// TODO: Do Flag check
|
||||
|
||||
// Preflight ok.
|
||||
s.State = SessionState_AUTHENTICATED_FULLY
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) HandleIdentification(identification string) error {
|
||||
if s.State != SessionState_EMPTY {
|
||||
return ErrIllegalStateAction // This step may only run on EMPTY sessions
|
||||
}
|
||||
// TODO: Handle Identification
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) HandlePassword(password string) error {
|
||||
if s.State != SessionState_UNAUTHENTICATED {
|
||||
return ErrIllegalStateAction // This step may only run on UNAUTHENTICATED sessions
|
||||
}
|
||||
|
||||
err := s.User.CheckPassword(password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Password Ok.
|
||||
s.AuthenticationMethod = AuthenticationMethod_PASSWORD
|
||||
|
||||
if !s.User.Needs2FA() {
|
||||
// No 2fa, jump to AUTHENTICATED_PENDING for preflight
|
||||
s.State = SessionState_AUTHENTICATED_PENDING
|
||||
return s.performPreflight()
|
||||
}
|
||||
|
||||
s.State = SessionState_AWAITING_FACTOR
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Passkey action
|
||||
|
||||
func (s *Session) HandleTOTP(otp string) error {
|
||||
if s.State != SessionState_AWAITING_FACTOR {
|
||||
return ErrIllegalStateAction
|
||||
}
|
||||
|
||||
err := s.User.ValidateTOTP(otp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// OTP Ok.
|
||||
s.SecondFactor = SecondFactor_TOTP
|
||||
|
||||
// Good to go for preflight.
|
||||
s.State = SessionState_AUTHENTICATED_PENDING
|
||||
return s.performPreflight()
|
||||
}
|
||||
|
||||
func (s *Session) HandleLock() error {
|
||||
if !s.IsAnyAuthenticated() {
|
||||
return ErrIllegalStateAction
|
||||
}
|
||||
// TODO: Handle Lock
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) HandleLogout() error {
|
||||
if !s.IsAnyAuthenticated() {
|
||||
return ErrIllegalStateAction
|
||||
}
|
||||
// TODO: Handle Logout
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) Destroy() error {
|
||||
// TODO: Destroy Session
|
||||
return nil
|
||||
}
|
31
internal/parameters/passwords.go
Normal file
31
internal/parameters/passwords.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package parameters
|
||||
|
||||
import "crypto/rand"
|
||||
|
||||
type PasswordParams struct {
|
||||
Memory uint32
|
||||
Iterations uint32
|
||||
Parallelism uint8
|
||||
SaltLength uint32
|
||||
KeyLength uint32
|
||||
}
|
||||
|
||||
func GetPasswordParams() *PasswordParams {
|
||||
return &PasswordParams{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 2,
|
||||
SaltLength: 16,
|
||||
KeyLength: 32,
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateRandomBytes(n uint32) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
4
internal/server/server.go
Normal file
4
internal/server/server.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
package server
|
||||
|
||||
func Serve() {
|
||||
}
|
9
main.go
Normal file
9
main.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import "git.1in9.net/raider/wroofauth/cmd"
|
||||
|
||||
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/session.proto
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
7
tools.go
Normal file
7
tools.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build tools
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/spf13/cobra-cli"
|
||||
)
|
Loading…
Add table
Reference in a new issue