[DEPRECATE] Remove migration tools from latest version of Authelia (#894)
* [DEPRECATE] Remove migration tools from latest version of Authelia Also update references to point to container version 4.14.2 for any of the migration examples. * [DOCS] Remove v4 release statement in README.mdpull/897/head
parent
54694c4fca
commit
9eb9d107f1
10
BREAKING.md
10
BREAKING.md
|
@ -54,16 +54,10 @@ supported by Authelia v4.
|
||||||
Example of usage:
|
Example of usage:
|
||||||
```
|
```
|
||||||
# Migrate a local database into the targeted database defined in config-v4.yml with Docker
|
# Migrate a local database into the targeted database defined in config-v4.yml with Docker
|
||||||
docker run --rm -v /path/to/config-v4.yml:/config.yml -v /old/db/path:/db authelia/authelia authelia migrate local --config=/config.yml --db-path=/db
|
docker run --rm -v /path/to/config-v4.yml:/config.yml -v /old/db/path:/db authelia/authelia:4.14.2 authelia migrate local --config=/config.yml --db-path=/db
|
||||||
|
|
||||||
# Migrate a mongo database into the targeted database defined in config-v4.yml with Docker
|
# Migrate a mongo database into the targeted database defined in config-v4.yml with Docker
|
||||||
docker run --rm -v /path/to/config-v4.yml:/config.yml authelia/authelia authelia migrate mongo --config=/config.yml --url=mongodb://myuser:mypassword@mymongo:27017 --database=authelia
|
docker run --rm -v /path/to/config-v4.yml:/config.yml authelia/authelia:4.14.2 authelia migrate mongo --config=/config.yml --url=mongodb://myuser:mypassword@mymongo:27017 --database=authelia
|
||||||
|
|
||||||
# Migrate a local database into the targeted database defined in config-v4.yml
|
|
||||||
authelia-scripts migrate local --config=/path/to/config-v4.yml --db-path=/old/db/path
|
|
||||||
|
|
||||||
# Migrate a mongo database into the targeted database defined in config-v4.yml
|
|
||||||
authelia-scripts migrate mongo --config=/path/to/config-v4.yml --url=mongodb://myuser:mypassword@mymongo:27017 --database=authelia
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Those commands migrate TOTP secrets, U2F devices, authentication traces and user preferences so
|
Those commands migrate TOTP secrets, U2F devices, authentication traces and user preferences so
|
||||||
|
|
|
@ -28,9 +28,6 @@ The architecture is shown in the diagram below.
|
||||||
<img src="./docs/images/archi.png"/>
|
<img src="./docs/images/archi.png"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**BREAKING NEWS: Authelia v4 has been released!
|
|
||||||
Please read BREAKING.md if you want to migrate from v3 to v4. Otherwise, start fresh in v4 and enjoy!**
|
|
||||||
|
|
||||||
**Authelia** can be installed as a standalone service from the [AUR](https://aur.archlinux.org/packages/authelia/), using a [Static binary](https://github.com/authelia/authelia/releases/latest), [Docker]
|
**Authelia** can be installed as a standalone service from the [AUR](https://aur.archlinux.org/packages/authelia/), using a [Static binary](https://github.com/authelia/authelia/releases/latest), [Docker]
|
||||||
or can also be deployed easily on [Kubernetes] leveraging ingress controllers and ingress configuration.
|
or can also be deployed easily on [Kubernetes] leveraging ingress controllers and ingress configuration.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
go run -tags migration cmd/authelia-scripts/*.go $*
|
go run cmd/authelia-scripts/*.go $*
|
|
@ -130,7 +130,7 @@ func main() {
|
||||||
|
|
||||||
cobraCommands = append(cobraCommands, command)
|
cobraCommands = append(cobraCommands, command)
|
||||||
}
|
}
|
||||||
cobraCommands = append(cobraCommands, commands.HashPasswordCmd, commands.MigrateCmd)
|
cobraCommands = append(cobraCommands, commands.HashPasswordCmd)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command")
|
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command")
|
||||||
rootCmd.AddCommand(cobraCommands...)
|
rootCmd.AddCommand(cobraCommands...)
|
||||||
|
|
|
@ -130,7 +130,7 @@ func main() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(versionCmd, commands.MigrateCmd, commands.HashPasswordCmd)
|
rootCmd.AddCommand(versionCmd, commands.HashPasswordCmd)
|
||||||
rootCmd.AddCommand(commands.CertificatesCmd)
|
rootCmd.AddCommand(commands.CertificatesCmd)
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration"
|
|
||||||
"github.com/authelia/authelia/internal/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MigrateCmd migration helper command.
|
|
||||||
var MigrateCmd *cobra.Command
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
MigrateCmd = &cobra.Command{
|
|
||||||
Use: "migrate",
|
|
||||||
Short: "helper function to migrate from v3 to v4",
|
|
||||||
}
|
|
||||||
MigrateCmd.AddCommand(MigrateLocalCmd, MigrateMongoCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TOTPSecretsV3 one entry of TOTP secrets in v3.
|
|
||||||
type TOTPSecretsV3 struct {
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
Secret struct {
|
|
||||||
Base32 string `json:"base32"`
|
|
||||||
} `json:"secret"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// U2FDeviceHandleV3 one entry of U2F device handle in v3.
|
|
||||||
type U2FDeviceHandleV3 struct {
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
Registration struct {
|
|
||||||
KeyHandle string `json:"keyHandle"`
|
|
||||||
PublicKey string `json:"publicKey"`
|
|
||||||
} `json:"registration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreferencesV3 one entry of preferences in v3.
|
|
||||||
type PreferencesV3 struct {
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticationTraceV3 one authentication trace in v3.
|
|
||||||
type AuthenticationTraceV3 struct {
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
Successful bool `json:"isAuthenticationSuccessful"`
|
|
||||||
Date struct {
|
|
||||||
Date int64 `json:"$$date"`
|
|
||||||
} `json:"date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeWebsafeBase64(s string) ([]byte, error) {
|
|
||||||
s = strings.ReplaceAll(s, "_", "/")
|
|
||||||
s = strings.ReplaceAll(s, "-", "+")
|
|
||||||
|
|
||||||
for len(s)%4 != 0 {
|
|
||||||
s += "="
|
|
||||||
}
|
|
||||||
|
|
||||||
return base64.StdEncoding.DecodeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createDBProvider(configurationPath string) storage.Provider {
|
|
||||||
config, _ := configuration.Read(configurationPath)
|
|
||||||
|
|
||||||
var dbProvider storage.Provider
|
|
||||||
if config.Storage.Local != nil {
|
|
||||||
dbProvider = storage.NewSQLiteProvider(config.Storage.Local.Path)
|
|
||||||
} else if config.Storage.MySQL != nil {
|
|
||||||
dbProvider = storage.NewMySQLProvider(*config.Storage.MySQL)
|
|
||||||
} else if config.Storage.PostgreSQL != nil {
|
|
||||||
dbProvider = storage.NewPostgreSQLProvider(*config.Storage.PostgreSQL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dbProvider
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/models"
|
|
||||||
"github.com/authelia/authelia/internal/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
var configurationPath string
|
|
||||||
var localDatabasePath string
|
|
||||||
|
|
||||||
// MigrateLocalCmd migration command.
|
|
||||||
var MigrateLocalCmd = &cobra.Command{
|
|
||||||
Use: "localdb",
|
|
||||||
Short: "Migrate data from v3 local database into database configured in v4 configuration file",
|
|
||||||
Run: migrateLocal,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
MigrateLocalCmd.PersistentFlags().StringVarP(&localDatabasePath, "db-path", "p", "", "The path to the v3 local database")
|
|
||||||
MigrateLocalCmd.MarkPersistentFlagRequired("db-path") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
|
|
||||||
MigrateLocalCmd.PersistentFlags().StringVarP(&configurationPath, "config", "c", "", "The configuration file of Authelia v4")
|
|
||||||
MigrateLocalCmd.MarkPersistentFlagRequired("config") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
}
|
|
||||||
|
|
||||||
// migrateLocal data from v3 to v4.
|
|
||||||
func migrateLocal(cmd *cobra.Command, args []string) {
|
|
||||||
dbProvider := createDBProvider(configurationPath)
|
|
||||||
|
|
||||||
migrateLocalTOTPSecret(dbProvider)
|
|
||||||
migrateLocalU2FSecret(dbProvider)
|
|
||||||
migrateLocalPreferences(dbProvider)
|
|
||||||
migrateLocalAuthenticationTraces(dbProvider)
|
|
||||||
// We don't need to migrate identity tokens
|
|
||||||
|
|
||||||
log.Println("Migration done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateLocalTOTPSecret(dbProvider storage.Provider) {
|
|
||||||
file, err := os.Open(path.Join(localDatabasePath, "totp_secrets"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
scanner.Split(bufio.ScanLines)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
data := scanner.Text()
|
|
||||||
|
|
||||||
entry := TOTPSecretsV3{}
|
|
||||||
json.Unmarshal([]byte(data), &entry) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
err := dbProvider.SaveTOTPSecret(entry.UserID, entry.Secret.Base32)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateLocalU2FSecret(dbProvider storage.Provider) {
|
|
||||||
file, err := os.Open(path.Join(localDatabasePath, "u2f_registrations"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
scanner.Split(bufio.ScanLines)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
data := scanner.Text()
|
|
||||||
|
|
||||||
entry := U2FDeviceHandleV3{}
|
|
||||||
json.Unmarshal([]byte(data), &entry) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
|
|
||||||
kH, err := decodeWebsafeBase64(entry.Registration.KeyHandle)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pK, err := decodeWebsafeBase64(entry.Registration.PublicKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbProvider.SaveU2FDeviceHandle(entry.UserID, kH, pK)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateLocalPreferences(dbProvider storage.Provider) {
|
|
||||||
file, err := os.Open(path.Join(localDatabasePath, "prefered_2fa_method")) //nolint:misspell
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
scanner.Split(bufio.ScanLines)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
data := scanner.Text()
|
|
||||||
|
|
||||||
entry := PreferencesV3{}
|
|
||||||
json.Unmarshal([]byte(data), &entry) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
err := dbProvider.SavePreferred2FAMethod(entry.UserID, entry.Method)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateLocalAuthenticationTraces(dbProvider storage.Provider) {
|
|
||||||
file, err := os.Open(path.Join(localDatabasePath, "authentication_traces"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
scanner.Split(bufio.ScanLines)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
data := scanner.Text()
|
|
||||||
|
|
||||||
entry := AuthenticationTraceV3{}
|
|
||||||
json.Unmarshal([]byte(data), &entry) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
|
|
||||||
attempt := models.AuthenticationAttempt{
|
|
||||||
Username: entry.UserID,
|
|
||||||
Successful: entry.Successful,
|
|
||||||
Time: time.Unix(entry.Date.Date/1000.0, 0),
|
|
||||||
}
|
|
||||||
err := dbProvider.AppendAuthenticationLog(attempt)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/models"
|
|
||||||
"github.com/authelia/authelia/internal/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mongoURL string
|
|
||||||
var mongoDatabase string
|
|
||||||
|
|
||||||
// MigrateMongoCmd migration command.
|
|
||||||
var MigrateMongoCmd = &cobra.Command{
|
|
||||||
Use: "mongo",
|
|
||||||
Short: "Migrate data from v3 mongo database into database configured in v4 configuration file",
|
|
||||||
Run: migrateMongo,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
MigrateMongoCmd.PersistentFlags().StringVar(&mongoURL, "url", "", "The address to the mongo server")
|
|
||||||
MigrateMongoCmd.MarkPersistentFlagRequired("url") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
|
|
||||||
MigrateMongoCmd.PersistentFlags().StringVar(&mongoDatabase, "database", "", "The mongo database")
|
|
||||||
MigrateMongoCmd.MarkPersistentFlagRequired("database") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
|
|
||||||
MigrateMongoCmd.PersistentFlags().StringVarP(&configurationPath, "config", "c", "", "The configuration file of Authelia v4")
|
|
||||||
MigrateMongoCmd.MarkPersistentFlagRequired("config") //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateMongo(cmd *cobra.Command, args []string) {
|
|
||||||
dbProvider := createDBProvider(configurationPath)
|
|
||||||
client, err := mongo.NewClient(options.Client().ApplyURI(mongoURL))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Connect(context.Background())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db := client.Database(mongoDatabase)
|
|
||||||
|
|
||||||
migrateMongoU2FDevices(db, dbProvider)
|
|
||||||
migrateMongoTOTPDevices(db, dbProvider)
|
|
||||||
migrateMongoPreferences(db, dbProvider)
|
|
||||||
migrateMongoAuthenticationTraces(db, dbProvider)
|
|
||||||
|
|
||||||
log.Println("Migration done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateMongoU2FDevices(db *mongo.Database, dbProvider storage.Provider) {
|
|
||||||
u2fCollection := db.Collection("u2f_registrations")
|
|
||||||
|
|
||||||
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer cur.Close(context.Background())
|
|
||||||
|
|
||||||
for cur.Next(context.Background()) {
|
|
||||||
var result U2FDeviceHandleV3
|
|
||||||
err := cur.Decode(&result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
kH, err := decodeWebsafeBase64(result.Registration.KeyHandle)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pK, err := decodeWebsafeBase64(result.Registration.PublicKey)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbProvider.SaveU2FDeviceHandle(result.UserID, kH, pK)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := cur.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateMongoTOTPDevices(db *mongo.Database, dbProvider storage.Provider) {
|
|
||||||
u2fCollection := db.Collection("totp_secrets")
|
|
||||||
|
|
||||||
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer cur.Close(context.Background())
|
|
||||||
|
|
||||||
for cur.Next(context.Background()) {
|
|
||||||
var result TOTPSecretsV3
|
|
||||||
err := cur.Decode(&result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbProvider.SaveTOTPSecret(result.UserID, result.Secret.Base32)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := cur.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateMongoPreferences(db *mongo.Database, dbProvider storage.Provider) {
|
|
||||||
u2fCollection := db.Collection("prefered_2fa_method") //nolint:misspell
|
|
||||||
|
|
||||||
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer cur.Close(context.Background())
|
|
||||||
|
|
||||||
for cur.Next(context.Background()) {
|
|
||||||
var result PreferencesV3
|
|
||||||
err := cur.Decode(&result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbProvider.SavePreferred2FAMethod(result.UserID, result.Method)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := cur.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateMongoAuthenticationTraces(db *mongo.Database, dbProvider storage.Provider) {
|
|
||||||
u2fCollection := db.Collection("authentication_traces")
|
|
||||||
|
|
||||||
cur, err := u2fCollection.Find(context.Background(), bson.D{})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer cur.Close(context.Background())
|
|
||||||
|
|
||||||
for cur.Next(context.Background()) {
|
|
||||||
var result AuthenticationTraceV3
|
|
||||||
err := cur.Decode(&result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
attempt := models.AuthenticationAttempt{
|
|
||||||
Username: result.UserID,
|
|
||||||
Successful: result.Successful,
|
|
||||||
Time: time.Unix(result.Date.Date/1000.0, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dbProvider.AppendAuthenticationLog(attempt)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := cur.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue