openvpn-user/src/commands.go

332 lines
8.7 KiB
Go

package src
import (
"database/sql"
"encoding/base32"
"fmt"
"github.com/dgryski/dgoogauth"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"os"
"strings"
"text/tabwriter"
)
func (oUser *OpenvpnUser) InitDb() {
// boolean fields are integer because of sqlite does not support boolean: 1 = true, 0 = false
_, err := oUser.Database.Exec("CREATE TABLE IF NOT EXISTS users(id integer not null primary key autoincrement, username string UNIQUE, password string, revoked integer default 0, deleted integer default 0)")
checkErr(err)
_, err = oUser.Database.Exec("CREATE TABLE IF NOT EXISTS migrations(id integer not null primary key autoincrement, name string)")
checkErr(err)
log.Infof("Database initialized at %v", oUser.Database.Driver())
}
func (oUser *OpenvpnUser) CreateUser(username, password string) (string, error) {
if !oUser.CheckUserExistent(username) {
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
_, err := oUser.Database.Exec("INSERT INTO users(username, password, secret, revoked, deleted, app_configured) VALUES ($1, $2, $3, 0, 0, 0)", username, string(hash), "")
checkErr(err)
return "User created", nil
} else {
return "", userAlreadyExistError
}
}
func (oUser *OpenvpnUser) DeleteUser(username string, force bool) (string, error) {
deleteQuery := "UPDATE users SET deleted = 1 WHERE username = $1"
if force {
deleteQuery = "DELETE FROM users WHERE username = $1"
}
res, err := oUser.Database.Exec(deleteQuery, username)
if err != nil {
return "", err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return "", err
}
if rowsAffected == 0 {
return "", userDeleteError
}
return "User deleted", nil
}
func (oUser *OpenvpnUser) RevokedUser(username string) (string, error) {
if !oUser.userDeleted(username) {
res, err := oUser.Database.Exec("UPDATE users SET revoked = 1 WHERE username = $1", username)
if err != nil {
return "", err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return "", err
}
if rowsAffected == 0 {
return "", userRevokeError
}
return "User revoked", nil
}
return "", userDeletedError
}
func (oUser *OpenvpnUser) RestoreUser(username string) (string, error) {
if !oUser.userDeleted(username) {
res, err := oUser.Database.Exec("UPDATE users SET revoked = 0 WHERE username = $1", username)
if err != nil {
return "", err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return "", err
}
if rowsAffected == 0 {
return "", userRestoreError
}
return "User restored", nil
}
return "", userDeletedError
}
func (oUser *OpenvpnUser) CheckUserExistent(username string) bool {
c := 0
_ = oUser.Database.QueryRow("SELECT count(*) FROM users WHERE username = $1", username).Scan(&c)
if c == 1 {
return true
} else {
return false
}
}
func (oUser *OpenvpnUser) userDeleted(username string) bool {
u := User{}
_ = oUser.Database.QueryRow("SELECT deleted FROM users WHERE username = $1", username).Scan(&u.deleted)
if u.deleted {
return true
} else {
return false
}
}
func (oUser *OpenvpnUser) userIsActive(username string) bool {
// return true if user exist and not deleted or revoked
u := User{}
err := oUser.Database.QueryRow("SELECT revoked,deleted FROM users WHERE username = $1", username).Scan(&u.revoked, &u.deleted)
if err != nil {
if err == sql.ErrNoRows {
return false
}
return false
}
if !u.revoked && !u.deleted {
return true
} else {
return false
}
}
func (oUser *OpenvpnUser) listUsers(all bool) []User {
var users []User
condition := "WHERE deleted = 0 AND revoked = 0"
if all {
condition = ""
}
query := "SELECT id, username, password, revoked, deleted, app_configured FROM users " + condition
rows, err := oUser.Database.Query(query)
checkErr(err)
for rows.Next() {
u := User{}
err = rows.Scan(&u.id, &u.name, &u.password, &u.revoked, &u.deleted, &u.appConfigured)
if err != nil {
//log.Error(err)
continue
}
users = append(users, u)
}
return users
}
func (oUser *OpenvpnUser) PrintUsers(all bool) {
ul := oUser.listUsers(all)
if len(ul) > 0 {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.TabIndent|tabwriter.Debug)
_, _ = fmt.Fprintln(w, "id\t username\t revoked\t deleted\t app_configured")
for _, u := range ul {
_, _ = fmt.Fprintf(w, "%d\t %s\t %v\t %v\t %v\n", u.id, u.name, u.revoked, u.deleted, u.appConfigured)
}
_ = w.Flush()
} else {
log.Print("No users created yet")
}
}
func (oUser *OpenvpnUser) ChangeUserPassword(username, password string) (string, error) {
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
_, err := oUser.Database.Exec("UPDATE users SET password = $1 WHERE username = $2", hash, username)
if err != nil {
return "", err
}
return "Password changed", nil
}
func (oUser *OpenvpnUser) RegisterOtpSecret(username, secret string) (string, error) {
if oUser.userIsActive(username) {
if secret == "generate" {
randomStr := randStr(6, "alphanum")
secret = base32.StdEncoding.EncodeToString([]byte(randomStr))
log.Debug("new generated secret for user %s: %s", username, secret)
}
_, err := oUser.Database.Exec("UPDATE users SET secret = $1 WHERE username = $2", secret, username)
if err != nil {
return "", err
}
return "Secret updated", nil
}
return "", userIsNotActiveError
}
func (oUser *OpenvpnUser) RegisterOtpApplication(username, totp string) (string, error) {
if oUser.userIsActive(username) {
appConfigured, appErr := oUser.IsSecondFactorEnabled(username)
if appErr != nil {
return "", appErr
}
if !appConfigured {
authOk, authErr := oUser.AuthUser(username, "", totp)
if authErr != nil {
return "", authErr
}
if authOk {
_, err := oUser.Database.Exec("UPDATE users SET app_configured = 1 WHERE username = $2")
if err != nil {
return "", err
}
return "OTP application configured", nil
}
}
return "OTP application already configured", nil
}
return "", userIsNotActiveError
}
func (oUser *OpenvpnUser) GetUserOtpSecret(username string) (string, error) {
if oUser.userIsActive(username) {
u := User{}
_ = oUser.Database.QueryRow("SELECT secret FROM users WHERE username = $1", username).Scan(&u.secret)
return u.secret, nil
}
return "", userIsNotActiveError
}
func (oUser *OpenvpnUser) IsSecondFactorEnabled(username string) (bool, error) {
if oUser.userIsActive(username) {
u := User{}
_ = oUser.Database.QueryRow("SELECT username, appConfigured FROM users WHERE username = $1", username).Scan(&u.name, &u.appConfigured)
if u.name == username {
return u.appConfigured, nil
}
return false, checkAppError
}
return false, userIsNotActiveError
}
func (oUser *OpenvpnUser) AuthUser(username, password, totp string) (bool, error) {
row := oUser.Database.QueryRow("SELECT id, username, password, revoked, deleted, secret, app_configured FROM users WHERE username = $1", username)
u := User{}
err := row.Scan(&u.id, &u.name, &u.password, &u.revoked, &u.deleted, &u.secret, &u.appConfigured)
if err != nil {
return false, err
}
if oUser.userIsActive(username) {
if password == "" && len(totp) > 0 {
if len(u.secret) == 0 {
return false, userSecretDoesNotExistError
}
otpConfig := &dgoogauth.OTPConfig{
Secret: strings.TrimSpace(u.secret),
WindowSize: 3,
HotpCounter: 0,
}
trimmedToken := strings.TrimSpace(totp)
ok, err := otpConfig.Authenticate(trimmedToken)
if err != nil {
log.Error(err)
}
if ok {
return true, nil
} else {
return false, tokenMismatchedError
}
} else if len(password) > 0 && totp == "" {
err = bcrypt.CompareHashAndPassword([]byte(u.password), []byte(password))
if err != nil {
return false, passwordMismatchedError
} else {
return true, nil
}
}
}
return false, userIsNotActiveError
}
func (oUser *OpenvpnUser) MigrateDb() {
var c int
var migrations []Migration
migrations = append(migrations, Migration{name: "users_add_secret_column_2022_11_10", sql: "ALTER TABLE users ADD COLUMN secret string"})
migrations = append(migrations, Migration{name: "users_add_2fa_column_2022_11_11", sql: "ALTER TABLE users ADD COLUMN app_configured integer default 0"})
for _, migration := range migrations {
c = -1
err := oUser.Database.QueryRow("SELECT count(*) FROM migrations WHERE name = $1", migration.name).Scan(&c)
if err != nil {
if err == sql.ErrNoRows {
continue
}
log.Fatal(err)
}
if c == 0 {
log.Info("Migrating database with new migration %s\n", migration.name)
_, err = oUser.Database.Exec(migration.sql)
checkErr(err)
_, err = oUser.Database.Exec("INSERT INTO migrations(name) VALUES ($1)", migration.name)
checkErr(err)
}
}
log.Info("Migrations are up to date")
}
func checkErr(err error) {
if err != nil {
fmt.Println(err)
}
}