From 71e830d52f5ec1dda734f022037cb8afd0f3ec65 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 1 Feb 2018 09:31:06 +0100 Subject: [PATCH] Simplify: use OAuth2 --- assets/templates/files/config.ovpn | 78 +++++++ assets/templates/layouts/admin.gohtml | 39 ---- assets/templates/layouts/application.gohtml | 8 +- assets/templates/shared/footer.gohtml | 13 -- assets/templates/shared/header.gohtml | 37 --- assets/templates/views/cert_list.gohtml | 24 -- assets/templates/views/client_list.gohtml | 65 ++++++ assets/templates/views/debug.gohtml | 17 -- assets/templates/views/forgot-password.gohtml | 24 -- assets/templates/views/register.gohtml | 32 --- handlers/auth.go | 215 +++++------------- handlers/cert.go | 103 +++++++-- handlers/converters.go | 64 ++++++ handlers/validators.go | 120 ++++++++++ main.go | 13 -- middleware/requirelogin.go | 2 +- models/model.go | 12 +- models/user.go | 55 ----- router/router.go | 26 +-- services/db.go | 79 +++---- services/email.go | 121 ---------- services/provider.go | 3 - services/sessions.go | 6 +- views/templates.go | 9 +- views/views.go | 2 +- 25 files changed, 524 insertions(+), 643 deletions(-) create mode 100644 assets/templates/files/config.ovpn delete mode 100644 assets/templates/layouts/admin.gohtml delete mode 100644 assets/templates/shared/footer.gohtml delete mode 100644 assets/templates/shared/header.gohtml delete mode 100644 assets/templates/views/cert_list.gohtml create mode 100644 assets/templates/views/client_list.gohtml delete mode 100644 assets/templates/views/debug.gohtml delete mode 100644 assets/templates/views/forgot-password.gohtml delete mode 100644 assets/templates/views/register.gohtml create mode 100644 handlers/converters.go create mode 100644 handlers/validators.go delete mode 100644 models/user.go delete mode 100644 services/email.go diff --git a/assets/templates/files/config.ovpn b/assets/templates/files/config.ovpn new file mode 100644 index 0000000..5f02dcb --- /dev/null +++ b/assets/templates/files/config.ovpn @@ -0,0 +1,78 @@ +{{ define "base" }} +client +dev tun +proto udp +remote 195.201.7.179 443 +resolv-retry infinite +nobind +persist-key +persist-tun + +cipher AES-256-CBC +auth SHA512 +ns-cert-type server +key-direction 1 +tls-version-min 1.2 +;comp-lzo +verb 3 + +route 172.31.1.100 255.255.255.255 net_gateway + + +-----BEGIN CERTIFICATE----- +MIIDwDCCAqigAwIBAgIJAMvRC7FajlAOMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNV +BAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjETMBEGA1UE +CgwKT25lT2ZmVGVjaDEWMBQGA1UECwwNSVQgZGVwYXJ0bWVudDEXMBUGA1UEAwwO +Y2EtY2VydGlmaWNhdGUwHhcNMTgwMTI1MTM0NjI3WhcNMjMwMTI0MTM0NjI3WjB1 +MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x +EzARBgNVBAoMCk9uZU9mZlRlY2gxFjAUBgNVBAsMDUlUIGRlcGFydG1lbnQxFzAV +BgNVBAMMDmNhLWNlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA5LzVrHqz33L5YiFs1HOZWvLht9yQ6+AxK1+RDZsx8490UEYvPnguyU/c +8NtaZPtWOg5Qvnh+0tHpLHV+3WbWyIObkix6b3U5EgR6Hgdf1zuzX7y/S2o7uPT1 +zkCgIi9EQfy0IDIhIErsO0dOWndFt/cAfrMaOx0LV/kzr9bKdgg7WLQoVzUgawZq +ROScZUogaElISxC/C77YaGg9V5sV9qTa3uZ9DxuESzXLGMDx3DJMjH+Yu+nhJjoc +isSxK5qEnfqWJhZgJFTAY2BRbcMFMieVz/+UGk2GDZf1tpMZOQKxwrNibe4HO8zo +lfhX+H+sb4QZCdn30eUGstK/jJdQrQIDAQABo1MwUTAdBgNVHQ4EFgQU9UASoCXR +ountXC2vQ4s9BT5qGRYwHwYDVR0jBBgwFoAU9UASoCXRountXC2vQ4s9BT5qGRYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA2YgYuFKMzoblpPf+ +VcyFKAXC9IoOJFeoA8FWMLBy38FedpCP+aFtlnG5eSLB/Xy7rdJK+7ASrdbAsFMD +U6P2guqUix4veIBZK0WLGTLfRKHQiOUqNP1zZpWsdrwUoUjGOEt4iqG9PCcaANSg +mOfl/BK+MtuRevF6Ry2JAZDArUXrXXjNdRXKB7iNc3Sd5icII53OGXXtn1ehzZXL +djbdz4MZa1kbA1ZlJVaYCRzOS/F9kU2aQceO17foxI5BvnOkpONLXDZHs61/KtYu +5z7hJoH49+4iyWZuRgWT/sq36qpvu+/f48JPxzqV94Jp77Z9BocTIjdfqHM++X9h +Yo95ZQ== +-----END CERTIFICATE----- + + + +{{ .Cert | html }} + + + +{{ .Key | html }} + + + +# +# 2048 bit OpenVPN static key +# +-----BEGIN OpenVPN Static key V1----- +187be23c2b3b0a6a9d79bc5b5c95b70b +b43b6b303e7c00eb75121d68df470ea3 +3cdd0ddedc273f5412f181709890ea32 +7086cbc5b21bbaf3dd231d115b5ba986 +1b1aee31bff9be5f6c6f6dd490d593a1 +eec50bb866558c6b2c6fe62ebfe125d9 +34b7115e72d94ce08cc9e1c4e8ccdbfe +a5ee19ac0aec60da63df881e3c2e7d4d +9c4f167ec1b46309f17c16c36683780b +bed7551ad7a3d526c19014567370122e +98ae0ae7fd83a8a6de09883fcc181b36 +a8465c0deda7a345ec3d16a4daf3fbf5 +23dc36a48e679c653b3cfc6dbaa150a7 +7ace46081d2c3712ce655f4b8211f674 +4d4688c2b3828f9208a80bf71e6e4554 +ae09b91154a435995439ad576fcc72c1 +-----END OpenVPN Static key V1----- + +{{ end }} diff --git a/assets/templates/layouts/admin.gohtml b/assets/templates/layouts/admin.gohtml deleted file mode 100644 index a6d2c64..0000000 --- a/assets/templates/layouts/admin.gohtml +++ /dev/null @@ -1,39 +0,0 @@ -{{ define "base" }} - - - - - - - - - - - Admin - - - - - {{ template "header" . }} - -
- {{range .flashes}} -
- {{end}} -
- -
- {{ template "content" . }} -
- - {{ template "footer" . }} - {{ template "sink" .}} - - - -{{ end }} - -{{ define "header"}}{{end}} -{{ define "content"}}{{end}} -{{ define "footer"}}{{end}} -{{ define "sink"}}{{end}} \ No newline at end of file diff --git a/assets/templates/layouts/application.gohtml b/assets/templates/layouts/application.gohtml index 7a231d9..573c397 100644 --- a/assets/templates/layouts/application.gohtml +++ b/assets/templates/layouts/application.gohtml @@ -28,14 +28,11 @@ {{end}}
{{ end }} - {{ template "header" . }}
{{ template "content" . }}
- {{ template "footer" . }} - {{ template "sink" .}} @@ -45,7 +42,4 @@ {{ end }} {{ define "meta"}}{{end}} -{{ define "header"}}{{end}} -{{ define "content"}}{{end}} -{{ define "footer"}}{{end}} -{{ define "sink"}}{{end}} \ No newline at end of file +{{ define "content"}}{{end}} \ No newline at end of file diff --git a/assets/templates/shared/footer.gohtml b/assets/templates/shared/footer.gohtml deleted file mode 100644 index 8e248f6..0000000 --- a/assets/templates/shared/footer.gohtml +++ /dev/null @@ -1,13 +0,0 @@ -{{ define "footer" }} - -{{ end }} \ No newline at end of file diff --git a/assets/templates/shared/header.gohtml b/assets/templates/shared/header.gohtml deleted file mode 100644 index 5d3cbe7..0000000 --- a/assets/templates/shared/header.gohtml +++ /dev/null @@ -1,37 +0,0 @@ -{{ define "header" }} - -{{ end }} \ No newline at end of file diff --git a/assets/templates/views/cert_list.gohtml b/assets/templates/views/cert_list.gohtml deleted file mode 100644 index f667653..0000000 --- a/assets/templates/views/cert_list.gohtml +++ /dev/null @@ -1,24 +0,0 @@ -{{ define "meta" }} - Log in -{{ end}} - -{{ define "content" }} -
-
-
-
-
-

Certificates

- {{ if .Certificates }} - {{ range .Certificates }} -
  • {{ .User }}@{{ .Name }}
  • - {{ end }} - {{ else }} -

    You don't have certificates yet!

    - {{ end }} -
    -
    -
    -
    -
    -{{ end}} diff --git a/assets/templates/views/client_list.gohtml b/assets/templates/views/client_list.gohtml new file mode 100644 index 0000000..1682c3c --- /dev/null +++ b/assets/templates/views/client_list.gohtml @@ -0,0 +1,65 @@ +{{ define "meta" }} + Log in +{{ end}} + +{{ define "content" }} +
    +
    +
    +
    +
    +

    Certificates

    + + + + + + + + + + + + + + + {{ range .Clients }} + + + + + + {{ end }} + +
    DeviceCreatedActions
    + + {{ .csrfField }}

    {{ $.username }}@{{ .Name }}

    +
    +

    + Download +

    +

    + + + + + +

    +
    +
    + +
    +
    +
    +
    +
    +{{ end}} diff --git a/assets/templates/views/debug.gohtml b/assets/templates/views/debug.gohtml deleted file mode 100644 index 4ca31c2..0000000 --- a/assets/templates/views/debug.gohtml +++ /dev/null @@ -1,17 +0,0 @@ -{{ define "meta" }} - Landing Page - -{{ end}} - -{{ define "content" }} -
    -
    -
    -
    -

    Hello, World!

    -

    Have some variables: {{.}}

    -
    -
    -
    -
    -{{ end}} diff --git a/assets/templates/views/forgot-password.gohtml b/assets/templates/views/forgot-password.gohtml deleted file mode 100644 index 2996737..0000000 --- a/assets/templates/views/forgot-password.gohtml +++ /dev/null @@ -1,24 +0,0 @@ -{{ define "meta" }}Forgot Password{{ end }} - -{{ define "content" }} -
    -
    -

    Reset Password

    -
    -
    - -
    - - - - -
    -
    - {{ .csrfField }} -
    - -
    -
    -
    -
    -{{ end }} \ No newline at end of file diff --git a/assets/templates/views/register.gohtml b/assets/templates/views/register.gohtml deleted file mode 100644 index 149e1de..0000000 --- a/assets/templates/views/register.gohtml +++ /dev/null @@ -1,32 +0,0 @@ -{{ define "meta" }}Log In{{ end }} - -{{ define "content" }} -
    -
    -

    Sign Up

    -
    -
    - -
    - - - - -
    -
    - {{ .csrfField }} -
    - -
    -
    -

    By signing up you agree to the Terms of Service

    -
    -
    -
    -
    -
    -

    - Already have an account? Log In -

    -
    -{{ end }} \ No newline at end of file diff --git a/handlers/auth.go b/handlers/auth.go index 504b27c..f2e9991 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -1,179 +1,86 @@ package handlers import ( - "bytes" + "encoding/json" "fmt" "net/http" - "time" + "os" "git.klink.asia/paul/certman/views" - - "github.com/go-chi/chi" - - "github.com/gorilla/securecookie" + "golang.org/x/oauth2" "git.klink.asia/paul/certman/services" - - "git.klink.asia/paul/certman/models" ) -func RegisterHandler(p *services.Provider) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - // Get parameters - email := req.Form.Get("email") - - user := models.User{} - user.Email = email - - // don't set a password, user will get password reset request via mail - user.HashedPassword = []byte{} - - err := p.DB.CreateUser(&user) - if err != nil { - panic(err.Error) - } - - if err := createPasswordReset(p, &user); err != nil { - p.Sessions.Flash(w, req, - services.Flash{ - Type: "danger", - Message: "The registration email could not be generated.", - }, - ) - http.Redirect(w, req, "/register", http.StatusFound) - } - - p.Sessions.Flash(w, req, - services.Flash{ - Type: "success", - Message: "The user was created. Check your inbox for the confirmation email.", - }, - ) - - http.Redirect(w, req, "/login", http.StatusFound) - return - } +var GitlabConfig = &oauth2.Config{ + ClientID: os.Getenv("OAUTH2_CLIENT_ID"), + ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"), + Scopes: []string{"read_user"}, + RedirectURL: os.Getenv("HOST") + "/login/oauth2/redirect", + Endpoint: oauth2.Endpoint{ + AuthURL: os.Getenv("OAUTH2_AUTH_URL"), + TokenURL: os.Getenv("OAUTH2_TOKEN_URL"), + }, } -func LoginHandler(p *services.Provider) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - // Get parameters - email := req.Form.Get("email") - password := req.Form.Get("password") - - user, err := p.DB.GetUserByEmail(email) - if err != nil { - // could not find user - p.Sessions.Flash( - w, req, services.Flash{ - Type: "warning", Message: "Invalid Email or Password.", - }, - ) - http.Redirect(w, req, "/login", http.StatusFound) - return - } - - if !user.EmailValid { - p.Sessions.Flash( - w, req, services.Flash{ - Type: "warning", Message: "You need to confirm your email before logging in.", - }, - ) - http.Redirect(w, req, "/login", http.StatusFound) - return - } - - if err := user.CheckPassword(password); err != nil { - // wrong password - p.Sessions.Flash( - w, req, services.Flash{ - Type: "warning", Message: "Invalid Email or Password.", - }, - ) - http.Redirect(w, req, "/login", http.StatusFound) - return - } - - // user is logged in, set cookie - p.Sessions.SetUserEmail(w, req, email) - - http.Redirect(w, req, "/certs", http.StatusSeeOther) - } -} - -func ConfirmEmailHandler(p *services.Provider) http.HandlerFunc { +func OAuth2Endpoint(p *services.Provider) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { v := views.NewWithSession(req, p.Sessions) - switch req.Method { - case "GET": - token := chi.URLParam(req, "token") - pwr, err := p.DB.GetPasswordResetByToken(token) - _ = pwr - if err != nil { - v.RenderError(w, 404) - return - } - v.Render(w, "email-set-password") - case "POST": - password := req.Form.Get("password") - token := req.Form.Get("token") - pwr, err := p.DB.GetPasswordResetByToken(token) - if err != nil { - v.RenderError(w, 404) - return - } + code := req.FormValue("code") - user, err := p.DB.GetUserByID(pwr.UserID) - if err != nil { - v.RenderError(w, 500) - return - } - - user.SetPassword(password) - - //err := p.DB.UpdateUser(user.ID, &user) - if err != nil { - v.RenderError(w, 500) - return - } - - err = p.DB.DeletePasswordResetsByUserID(pwr.UserID) - - default: - v.RenderError(w, 405) + // exchange code for token + accessToken, err := GitlabConfig.Exchange(oauth2.NoContext, code) + if err != nil { + fmt.Println(err) + http.NotFound(w, req) + return } - // try to get post params + if accessToken.Valid() { + // generate a client using the access token + httpClient := GitlabConfig.Client(oauth2.NoContext, accessToken) - fmt.Fprintln(w, "Okay.") + apiRequest, err := http.NewRequest("GET", "https://git.klink.asia/api/v4/user", nil) + if err != nil { + v.RenderError(w, http.StatusNotFound) + return + } + + resp, err := httpClient.Do(apiRequest) + if err != nil { + fmt.Println(err.Error()) + v.RenderError(w, http.StatusInternalServerError) + return + } + + var user struct { + Username string `json:"username"` + } + + err = json.NewDecoder(resp.Body).Decode(&user) + if err != nil { + fmt.Println(err.Error()) + v.RenderError(w, http.StatusInternalServerError) + return + } + + if user.Username != "" { + p.Sessions.SetUsername(w, req, user.Username) + http.Redirect(w, req, "/certs", http.StatusFound) + return + } + + fmt.Println(err.Error()) + v.RenderError(w, http.StatusInternalServerError) + return + } } } -func createPasswordReset(p *services.Provider, user *models.User) error { - // create the reset request - pwr := models.PasswordReset{ - UserID: user.ID, - Token: string(securecookie.GenerateRandomKey(32)), - ValidUntil: time.Now().Add(6 * time.Hour), +func GetLoginHandler(p *services.Provider) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + authURL := GitlabConfig.AuthCodeURL("", oauth2.AccessTypeOnline) + http.Redirect(w, req, authURL, http.StatusFound) } - - if err := p.DB.CreatePasswordReset(&pwr); err != nil { - return err - } - - var subject string - var text *bytes.Buffer - - if user.EmailValid { - subject = "Password reset" - text.WriteString("Somebody (hopefully you) has requested a password reset.\nClick below to reset your password:\n") - } else { - // If the user email has not been confirmed yet, send out - // "mail confirmation"-mail instead - subject = "Email confirmation" - text.WriteString("Hello, thanks you for signing up!\nClick below to verify this email address\n") - } - - return p.Email.Send(user.Email, subject, text.String(), "") } diff --git a/handlers/cert.go b/handlers/cert.go index 5900289..551f180 100644 --- a/handlers/cert.go +++ b/handlers/cert.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "crypto/ecdsa" "crypto/rand" "crypto/rsa" @@ -15,25 +16,38 @@ import ( "git.klink.asia/paul/certman/models" "git.klink.asia/paul/certman/services" + "github.com/go-chi/chi" "git.klink.asia/paul/certman/views" ) -func ListCertHandler(p *services.Provider) http.HandlerFunc { +func ListClientsHandler(p *services.Provider) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { v := views.NewWithSession(req, p.Sessions) - v.Render(w, "cert_list") + + username := p.Sessions.GetUsername(req) + + clients, _ := p.DB.ListClientsForUser(username, 100, 0) + + v.Vars["Clients"] = clients + v.Render(w, "client_list") } } func CreateCertHandler(p *services.Provider) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - email := p.Sessions.GetUserEmail(req) + username := p.Sessions.GetUsername(req) certname := req.FormValue("certname") - user, err := p.DB.GetUserByEmail(email) - if err != nil { - fmt.Printf("Could not fetch user for mail %s\n", email) + if !IsByteLength(certname, 2, 64) || !IsAlphanumeric(certname) { + p.Sessions.Flash(w, req, + services.Flash{ + Type: "danger", + Message: "The certificate name can only contain letters and numbers", + }, + ) + http.Redirect(w, req, "/certs", http.StatusFound) + return } // Load CA master certificate @@ -47,29 +61,45 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatalf("Could not generate keypair: %s", err) + p.Sessions.Flash(w, req, + services.Flash{ + Type: "danger", + Message: "The certificate key could not be generated", + }, + ) + http.Redirect(w, req, "/certs", http.StatusFound) + return } // Generate Certificate - derBytes, err := CreateCertificate(key, caCert, caKey) + commonName := fmt.Sprintf("%s@%s", username, certname) + derBytes, err := CreateCertificate(commonName, key, caCert, caKey) // Initialize new client config client := models.Client{ Name: certname, PrivateKey: x509.MarshalPKCS1PrivateKey(key), Cert: derBytes, - UserID: user.ID, + User: username, } // Insert client into database - _ = client - //if err := p.DB.Create(&client).Error; err != nil { - // panic(err.Error()) - //} + if err := p.DB.CreateClient(&client); err != nil { + log.Println(err.Error()) + p.Sessions.Flash(w, req, + services.Flash{ + Type: "danger", + Message: "The certificate could not be added to the database", + }, + ) + http.Redirect(w, req, "/certs", http.StatusFound) + return + } p.Sessions.Flash(w, req, services.Flash{ Type: "success", - Message: "The certificate was created successfully.", + Message: "The certificate was created successfully", }, ) @@ -79,13 +109,42 @@ func CreateCertHandler(p *services.Provider) http.HandlerFunc { func DownloadCertHandler(p *services.Provider) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - //v := views.New(req) - // - //derBytes, err := CreateCertificate(key, caCert, caKey) - //pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - // - //pkBytes := x509.MarshalPKCS1PrivateKey(key) - //pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkBytes}) + v := views.New(req) + // detemine own username + username := p.Sessions.GetUsername(req) + name := chi.URLParam(req, "name") + + client, err := p.DB.GetClientByNameUser(name, username) + if err != nil { + v.RenderError(w, http.StatusNotFound) + return + } + + // cbuf and kbuf are buffers in which the PEM certificates are + // rendered into + var cbuf = new(bytes.Buffer) + var kbuf = new(bytes.Buffer) + + pem.Encode(cbuf, &pem.Block{Type: "CERTIFICATE", Bytes: client.Cert}) + pem.Encode(kbuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: client.PrivateKey}) + + vars := map[string]string{ + "Cert": cbuf.String(), + "Key": kbuf.String(), + } + + t, err := views.GetTemplate("config.ovpn") + if err != nil { + log.Printf("Error loading certificate template: %s", err) + v.RenderError(w, http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/x-openvpn-profile") + w.Header().Set("Content-Disposition", "attachment; filename=\"config.ovpn\"") + w.WriteHeader(http.StatusOK) + log.Println(vars) + t.Execute(w, vars) return } } @@ -117,10 +176,10 @@ func loadX509KeyPair(certFile, keyFile string) (*x509.Certificate, *rsa.PrivateK } // CreateCertificate creates a CA-signed certificate -func CreateCertificate(key interface{}, caCert *x509.Certificate, caKey interface{}) ([]byte, error) { +func CreateCertificate(commonName string, key interface{}, caCert *x509.Certificate, caKey interface{}) ([]byte, error) { subj := caCert.Subject // .. except for the common name - subj.CommonName = "clientName" + subj.CommonName = commonName serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) diff --git a/handlers/converters.go b/handlers/converters.go new file mode 100644 index 0000000..d5809f3 --- /dev/null +++ b/handlers/converters.go @@ -0,0 +1,64 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" +) + +// ToString convert the input to a string. +func ToString(obj interface{}) string { + res := fmt.Sprintf("%v", obj) + return string(res) +} + +// ToJSON convert the input to a valid JSON string +func ToJSON(obj interface{}) (string, error) { + res, err := json.Marshal(obj) + if err != nil { + res = []byte("") + } + return string(res), err +} + +// ToFloat convert the input string to a float, or 0.0 if the input is not a float. +func ToFloat(str string) (float64, error) { + res, err := strconv.ParseFloat(str, 64) + if err != nil { + res = 0.0 + } + return res, err +} + +// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer. +func ToInt(value interface{}) (res int64, err error) { + val := reflect.ValueOf(value) + + switch value.(type) { + case int, int8, int16, int32, int64: + res = val.Int() + case uint, uint8, uint16, uint32, uint64: + res = int64(val.Uint()) + case string: + if IsInt(val.String()) { + res, err = strconv.ParseInt(val.String(), 0, 64) + if err != nil { + res = 0 + } + } else { + err = fmt.Errorf("math: square root of negative number %g", value) + res = 0 + } + default: + err = fmt.Errorf("math: square root of negative number %g", value) + res = 0 + } + + return +} + +// ToBoolean convert the input string to a boolean. +func ToBoolean(str string) (bool, error) { + return strconv.ParseBool(str) +} diff --git a/handlers/validators.go b/handlers/validators.go new file mode 100644 index 0000000..9d85558 --- /dev/null +++ b/handlers/validators.go @@ -0,0 +1,120 @@ +package handlers + +import "regexp" + +const ( + Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$" + ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$" + ISBN13 string = "^(?:[0-9]{13})$" + UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + Alpha string = "^[a-zA-Z]+$" + Alphanumeric string = "^[a-zA-Z0-9]+$" + Numeric string = "^[0-9]+$" + Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" + Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" + Hexadecimal string = "^[0-9a-fA-F]+$" + Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" + ASCII string = "^[\x00-\x7F]+$" + Multibyte string = "[^\x00-\x7F]" + FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" + HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" + Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + PrintableASCII string = "^[\x20-\x7E]+$" + DataURI string = "^data:.+\\/(.+);base64$" + Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" + Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" + DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$` + IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))` + URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)` + URLUsername string = `(\S+(:\S*)?@)` + URLPath string = `((\/|\?|#)[^\s]*)` + URLPort string = `(:(\d{1,5}))` + URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))` + URLSubdomain string = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))` + URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$` + SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` + WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` + UnixPath string = `^(/[^/\x00]*)+/?$` + Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$" + tagName string = "valid" + hasLowerCase string = ".*[[:lower:]]" + hasUpperCase string = ".*[[:upper:]]" +) + +var ( + rxEmail = regexp.MustCompile(Email) + rxCreditCard = regexp.MustCompile(CreditCard) + rxISBN10 = regexp.MustCompile(ISBN10) + rxISBN13 = regexp.MustCompile(ISBN13) + rxUUID3 = regexp.MustCompile(UUID3) + rxUUID4 = regexp.MustCompile(UUID4) + rxUUID5 = regexp.MustCompile(UUID5) + rxUUID = regexp.MustCompile(UUID) + rxAlpha = regexp.MustCompile(Alpha) + rxAlphanumeric = regexp.MustCompile(Alphanumeric) + rxNumeric = regexp.MustCompile(Numeric) + rxInt = regexp.MustCompile(Int) + rxFloat = regexp.MustCompile(Float) + rxHexadecimal = regexp.MustCompile(Hexadecimal) + rxHexcolor = regexp.MustCompile(Hexcolor) + rxRGBcolor = regexp.MustCompile(RGBcolor) + rxASCII = regexp.MustCompile(ASCII) + rxPrintableASCII = regexp.MustCompile(PrintableASCII) + rxMultibyte = regexp.MustCompile(Multibyte) + rxFullWidth = regexp.MustCompile(FullWidth) + rxHalfWidth = regexp.MustCompile(HalfWidth) + rxBase64 = regexp.MustCompile(Base64) + rxDataURI = regexp.MustCompile(DataURI) + rxLatitude = regexp.MustCompile(Latitude) + rxLongitude = regexp.MustCompile(Longitude) + rxDNSName = regexp.MustCompile(DNSName) + rxURL = regexp.MustCompile(URL) + rxSSN = regexp.MustCompile(SSN) + rxWinPath = regexp.MustCompile(WinPath) + rxUnixPath = regexp.MustCompile(UnixPath) + rxSemver = regexp.MustCompile(Semver) + rxHasLowerCase = regexp.MustCompile(hasLowerCase) + rxHasUpperCase = regexp.MustCompile(hasUpperCase) +) + +// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid. +func IsAlphanumeric(str string) bool { + if IsNull(str) { + return true + } + return rxAlphanumeric.MatchString(str) +} + +// ByteLength check string's length +func ByteLength(str string, params ...string) bool { + if len(params) == 2 { + min, _ := ToInt(params[0]) + max, _ := ToInt(params[1]) + return len(str) >= int(min) && len(str) <= int(max) + } + + return false +} + +// IsInt check if the string is an integer. Empty string is valid. +func IsInt(str string) bool { + if IsNull(str) { + return true + } + return rxInt.MatchString(str) +} + +// IsNull check if the string is null. +func IsNull(str string) bool { + return len(str) == 0 +} + +// IsByteLength check if the string's length (in bytes) falls in a range. +func IsByteLength(str string, min, max int) bool { + return len(str) >= min && len(str) <= max +} diff --git a/main.go b/main.go index 872ccea..8ac7b62 100644 --- a/main.go +++ b/main.go @@ -29,23 +29,10 @@ func main() { HttpOnly: true, Lifetime: 24 * time.Hour, }, - Email: &services.EmailConfig{ - SMTPEnabled: false, - SMTPServer: "example.com", - SMTPPort: 25, - SMTPUsername: "test", - SMTPPassword: "password", - SMTPTimeout: 5 * time.Second, - From: "Mailtest ", - }, } serviceProvider := services.NewProvider(&c) - // Start the mail daemon, which re-uses connections to send mails to the - // SMTP server - go serviceProvider.Email.Daemon() - // load and parse template files views.LoadTemplates() diff --git a/middleware/requirelogin.go b/middleware/requirelogin.go index 98bbac7..1f18f30 100644 --- a/middleware/requirelogin.go +++ b/middleware/requirelogin.go @@ -11,7 +11,7 @@ import ( func RequireLogin(sessions *services.Sessions) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, req *http.Request) { - if username := sessions.GetUserEmail(req); username == "" { + if username := sessions.GetUsername(req); username == "" { http.Redirect(w, req, "/login", http.StatusFound) } diff --git a/models/model.go b/models/model.go index 68c2719..9dc0ebb 100644 --- a/models/model.go +++ b/models/model.go @@ -23,17 +23,17 @@ type Model struct { // Client represent the OpenVPN client configuration type Client struct { Model - Name string - User User - UserID uint + Name string `gorm:"index;unique_index:idx_name_user"` + User string `gorm:"index;unique_index:idx_name_user"` Cert []byte PrivateKey []byte } type ClientProvider interface { CountClients() (uint, error) - CreateClient(*User) (*User, error) - ListClients(count, offset int) ([]*User, error) - GetClientByID(id uint) (*User, error) + CreateClient(*Client) (*Client, error) + ListClients(count, offset int) ([]*Client, error) + ListClientsForUser(user string, count, offset int) ([]*Client, error) + GetClientByID(id uint) (*Client, error) DeleteClient(id uint) error } diff --git a/models/user.go b/models/user.go deleted file mode 100644 index 467fd15..0000000 --- a/models/user.go +++ /dev/null @@ -1,55 +0,0 @@ -package models - -import ( - "time" - - "golang.org/x/crypto/bcrypt" -) - -// User represents a User of the system which is able to log in -type User struct { - Model - Email string - EmailValid bool - DisplayName string - HashedPassword []byte - IsAdmin bool -} - -// SetPassword sets the password of an user struct, but does not save it yet -func (u *User) SetPassword(password string) error { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return err - } - u.HashedPassword = bytes - return nil -} - -// CheckPassword compares a supplied plain text password with the internally -// stored password hash, returns error=nil on success. -func (u *User) CheckPassword(password string) error { - return bcrypt.CompareHashAndPassword(u.HashedPassword, []byte(password)) -} - -type UserProvider interface { - CountUsers() (uint, error) - CreateUser(*User) error - ListUsers(count, offset int) ([]*User, error) - GetUserByID(id uint) (*User, error) - GetUserByEmail(email string) (*User, error) - DeleteUser(id uint) error -} - -type PasswordReset struct { - Model - User *User - UserID uint - Token string - ValidUntil time.Time -} - -type PasswordResetProvider interface { - CreatePasswordReset(*PasswordReset) error - GetPasswordResetByToken(token string) (*PasswordReset, error) -} diff --git a/router/router.go b/router/router.go index c9c1a64..5ca4b0b 100644 --- a/router/router.go +++ b/router/router.go @@ -51,34 +51,18 @@ func HandleRoutes(provider *services.Provider) http.Handler { )) } - r.HandleFunc("/", v("debug")) - - r.Route("/register", func(r chi.Router) { - r.Get("/", v("register")) - r.Post("/", handlers.RegisterHandler(provider)) - }) + r.HandleFunc("/", http.RedirectHandler("certs", http.StatusFound).ServeHTTP) r.Route("/login", func(r chi.Router) { - r.Get("/", v("login")) - r.Post("/", handlers.LoginHandler(provider)) - }) - - r.Post("/confirm-email/{token}", handlers.ConfirmEmailHandler(provider)) - - r.Route("/forgot-password", func(r chi.Router) { - r.Get("/", v("forgot-password")) - r.Post("/", handlers.LoginHandler(provider)) + r.Get("/", handlers.GetLoginHandler(provider)) + r.Get("/oauth2/redirect", handlers.OAuth2Endpoint(provider)) }) r.Route("/certs", func(r chi.Router) { r.Use(mw.RequireLogin(provider.Sessions)) - r.Get("/", handlers.ListCertHandler(provider)) + r.Get("/", handlers.ListClientsHandler(provider)) r.Post("/new", handlers.CreateCertHandler(provider)) - r.HandleFunc("/download/{ID}", handlers.DownloadCertHandler(provider)) - }) - - r.HandleFunc("/500", func(w http.ResponseWriter, req *http.Request) { - panic("500") + r.HandleFunc("/download/{name}", handlers.DownloadCertHandler(provider)) }) }) diff --git a/services/db.go b/services/db.go index b0c26bd..8fd18c1 100644 --- a/services/db.go +++ b/services/db.go @@ -34,7 +34,7 @@ func NewDB(conf *DBConfig) *DB { } // Migrate models - db.AutoMigrate(models.User{}, models.Client{}) + db.AutoMigrate(models.Client{}) db.LogMode(conf.Log) return &DB{ @@ -43,64 +43,53 @@ func NewDB(conf *DBConfig) *DB { } } -// CountUsers returns the number of Users in the datastore -func (db *DB) CountUsers() (uint, error) { +// CountClients returns the number of clients in the datastore +func (db *DB) CountClients() (uint, error) { var count uint - err := db.gorm.Find(&models.User{}).Count(&count).Error + err := db.gorm.Find(&models.Client{}).Count(&count).Error return count, err } -// CreateUser inserts a user into the datastore -func (db *DB) CreateUser(user *models.User) error { - err := db.gorm.Create(&user).Error +// CreateClient inserts a client into the datastore +func (db *DB) CreateClient(client *models.Client) error { + err := db.gorm.Create(&client).Error return err } -// ListUsers returns a slice of 'count' users, starting at 'offset' -func (db *DB) ListUsers(count, offset int) ([]*models.User, error) { - var users = make([]*models.User, 0) +// ListClients returns a slice of 'count' client, starting at 'offset' +func (db *DB) ListClients(count, offset int) ([]*models.Client, error) { + var clients = make([]*models.Client, 0) - err := db.gorm.Find(&users).Limit(count).Offset(offset).Error + err := db.gorm.Find(&clients).Limit(count).Offset(offset).Error - return users, err + return clients, err } -// GetUserByID returns a single user by ID -func (db *DB) GetUserByID(id uint) (*models.User, error) { - var user models.User - err := db.gorm.Where("id = ?", id).First(&user).Error - return &user, err +// ListClientsForUser returns a slice of 'count' client for user 'user', starting at 'offset' +func (db *DB) ListClientsForUser(user string, count, offset int) ([]*models.Client, error) { + var clients = make([]*models.Client, 0) + + err := db.gorm.Find(&clients).Where("user = ?", user).Limit(count).Offset(offset).Error + + return clients, err } -// GetUserByEmail returns a single user by email -func (db *DB) GetUserByEmail(email string) (*models.User, error) { - var user models.User - err := db.gorm.Where("email = ?", email).First(&user).Error - return &user, err +// GetClientByID returns a single client by ID +func (db *DB) GetClientByID(id uint) (*models.Client, error) { + var client models.Client + err := db.gorm.Where("id = ?", id).First(&client).Error + return &client, err } -// DeleteUser removes a user from the datastore -func (db *DB) DeleteUser(id uint) error { - var user models.User - err := db.gorm.Where("id = ?", id).Delete(&user).Error - return err -} - -// CreatePasswordReset creates a new password reset token -func (db *DB) CreatePasswordReset(pwReset *models.PasswordReset) error { - err := db.gorm.Create(&pwReset).Error - return err -} - -// GetPasswordResetByToken retrieves a PasswordReset by token -func (db *DB) GetPasswordResetByToken(token string) (*models.PasswordReset, error) { - var pwReset models.PasswordReset - err := db.gorm.Where("token = ?", token).First(&pwReset).Error - return &pwReset, err -} - -// DeletePasswordResetsByUserID deletes all pending password resets for a user -func (db *DB) DeletePasswordResetsByUserID(uid uint) error { - err := db.gorm.Where("user_id = ?", uid).Delete(&models.PasswordReset{}).Error +// GetClientByNameUser returns a single client by ID +func (db *DB) GetClientByNameUser(name, user string) (*models.Client, error) { + var client models.Client + err := db.gorm.Where("name = ?", name).Where("user = ?", user).First(&client).Error + return &client, err +} + +// DeleteClient removes a client from the datastore +func (db *DB) DeleteClient(id uint) error { + err := db.gorm.Where("id = ?", id).Delete(&models.Client{}).Error return err } diff --git a/services/email.go b/services/email.go deleted file mode 100644 index baea8d9..0000000 --- a/services/email.go +++ /dev/null @@ -1,121 +0,0 @@ -package services - -import ( - "errors" - "log" - "time" - - "github.com/go-mail/mail" -) - -var ( - ErrMailUninitializedConfig = errors.New("Mail: uninitialized config") -) - -type EmailConfig struct { - From string - SMTPEnabled bool - SMTPServer string - SMTPPort int - SMTPUsername string - SMTPPassword string - SMTPTimeout time.Duration -} - -type Email struct { - config *EmailConfig - - mailChan chan *mail.Message -} - -func NewEmail(conf *EmailConfig) *Email { - if conf == nil { - log.Println(ErrMailUninitializedConfig) - } - - return &Email{ - config: conf, - mailChan: make(chan *mail.Message, 4), - } -} - -// Send sends an email to the receiver -func (email *Email) Send(to, subject, text, html string) error { - if email.config == nil { - log.Print("Error: trying to send mail with uninitialized config.") - return ErrMailUninitializedConfig - } - - if !email.config.SMTPEnabled { - log.Printf("SMTP is disabled in config, printing out email text instead:\nTo: %s\n%s", to, text) - } - - m := mail.NewMessage() - m.SetHeader("From", email.config.From) - m.SetHeader("To", to) - m.SetHeader("Subject", subject) - m.SetBody("text/plain", text) - - if len(html) > 0 { - m.AddAlternative("text/html", html) - } - - // put email in chan - email.mailChan <- m - return nil -} - -// Daemon is a function that takes Mail and sends it without blocking. -// WIP -func (email *Email) Daemon() { - if email.config == nil { - log.Print("Error: trying to set up mail deamon with uninitialized config.") - return - } - - if !email.config.SMTPEnabled { - log.Print("SMTP is disabled in config, emails will be printed instead.") - return - } - - log.Print("Running mail sending routine") - - d := mail.NewDialer( - email.config.SMTPServer, - email.config.SMTPPort, - email.config.SMTPUsername, - email.config.SMTPPassword) - - var s mail.SendCloser - var err error - open := false - for { - select { - case m, ok := <-email.mailChan: - if !ok { - // channel is closed - log.Print("Channel closed") - return - } - if !open { - if s, err = d.Dial(); err != nil { - log.Print(err) - return - } - open = true - } - log.Printf("Trying to send mail") - if err := mail.Send(s, m); err != nil { - log.Printf("Mail: %s", err) - } - // Close the connection if no email was sent in the last X seconds. - case <-time.After(email.config.SMTPTimeout): - if open { - if err := s.Close(); err != nil { - log.Printf("Mail: Failed to close connection: %s", err) - } - open = false - } - } - } -} diff --git a/services/provider.go b/services/provider.go index ab2abaa..87fde60 100644 --- a/services/provider.go +++ b/services/provider.go @@ -3,13 +3,11 @@ package services type Config struct { DB *DBConfig Sessions *SessionsConfig - Email *EmailConfig } type Provider struct { DB *DB Sessions *Sessions - Email *Email } // NewProvider returns the ServiceProvider @@ -18,7 +16,6 @@ func NewProvider(conf *Config) *Provider { provider.DB = NewDB(conf.DB) provider.Sessions = NewSessions(conf.Sessions) - provider.Email = NewEmail(conf.Email) return provider } diff --git a/services/sessions.go b/services/sessions.go index 36ee58b..9364e0f 100644 --- a/services/sessions.go +++ b/services/sessions.go @@ -49,7 +49,7 @@ func NewSessions(conf *SessionsConfig) *Sessions { return &Sessions{store} } -func (store *Sessions) GetUserEmail(req *http.Request) string { +func (store *Sessions) GetUsername(req *http.Request) string { if store == nil { // if store was not initialized, all requests fail log.Println("Zero pointer when checking session for username") @@ -69,7 +69,7 @@ func (store *Sessions) GetUserEmail(req *http.Request) string { return email } -func (store *Sessions) SetUserEmail(w http.ResponseWriter, req *http.Request, email string) { +func (store *Sessions) SetUsername(w http.ResponseWriter, req *http.Request, username string) { if store == nil { // if store was not initialized, do nothing return @@ -80,7 +80,7 @@ func (store *Sessions) SetUserEmail(w http.ResponseWriter, req *http.Request, em // renew token to avoid session pinning/fixation attack sess.RenewToken(w) - sess.PutString(w, UserEmailKey, email) + sess.PutString(w, UserEmailKey, username) } diff --git a/views/templates.go b/views/templates.go index 9d000ff..ec1655d 100644 --- a/views/templates.go +++ b/views/templates.go @@ -22,12 +22,11 @@ func LoadTemplates() { "404": newTemplate("layouts/application.gohtml", "errors/404.gohtml"), "500": newTemplate("layouts/application.gohtml", "errors/500.gohtml"), - "login": newTemplate("layouts/auth.gohtml", "views/login.gohtml"), - "register": newTemplate("layouts/auth.gohtml", "views/register.gohtml"), - "forgot-password": newTemplate("layouts/auth.gohtml", "views/forgot-password.gohtml"), + "login": newTemplate("layouts/auth.gohtml", "views/login.gohtml"), - "debug": newTemplate("layouts/application.gohtml", "shared/header.gohtml", "shared/footer.gohtml", "views/debug.gohtml"), - "cert_list": newTemplate("layouts/application.gohtml", "shared/header.gohtml", "shared/footer.gohtml", "views/cert_list.gohtml"), + "client_list": newTemplate("layouts/application.gohtml", "views/client_list.gohtml"), + + "config.ovpn": newTemplate("files/config.ovpn"), } return } diff --git a/views/views.go b/views/views.go index c0e33e7..f5185e6 100644 --- a/views/views.go +++ b/views/views.go @@ -46,7 +46,7 @@ func NewWithSession(req *http.Request, sessionStore *services.Sessions) *View { "Env": "develop", }, "flashes": []services.Flash{}, - "username": sessionStore.GetUserEmail(req), + "username": sessionStore.GetUsername(req), }, } }