mirror of
https://github.com/portainer/portainer.git
synced 2025-08-04 21:35:23 +02:00
feat(global): add authentication support with single admin account
This commit is contained in:
parent
1e5207517d
commit
4e77c72fa2
35 changed files with 1475 additions and 220 deletions
41
api/api.go
41
api/api.go
|
@ -2,6 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/gorilla/securecookie"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -15,6 +17,8 @@ type (
|
|||
dataPath string
|
||||
tlsConfig *tls.Config
|
||||
templatesURL string
|
||||
dataStore *dataStore
|
||||
secret []byte
|
||||
}
|
||||
|
||||
apiConfig struct {
|
||||
|
@ -31,7 +35,21 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
const (
|
||||
datastoreFileName = "portainer.db"
|
||||
)
|
||||
|
||||
var (
|
||||
errSecretKeyGeneration = errors.New("Unable to generate secret key to sign JWT")
|
||||
)
|
||||
|
||||
func (a *api) run(settings *Settings) {
|
||||
err := a.initDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer a.cleanUp()
|
||||
|
||||
handler := a.newHandler(settings)
|
||||
log.Printf("Starting portainer on %s", a.bindAddress)
|
||||
if err := http.ListenAndServe(a.bindAddress, handler); err != nil {
|
||||
|
@ -39,12 +57,34 @@ func (a *api) run(settings *Settings) {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *api) cleanUp() {
|
||||
a.dataStore.cleanUp()
|
||||
}
|
||||
|
||||
func (a *api) initDatabase() error {
|
||||
dataStore, err := newDataStore(a.dataPath + "/" + datastoreFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dataStore.initDataStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.dataStore = dataStore
|
||||
return nil
|
||||
}
|
||||
|
||||
func newAPI(apiConfig apiConfig) *api {
|
||||
endpointURL, err := url.Parse(apiConfig.Endpoint)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
secret := securecookie.GenerateRandomKey(32)
|
||||
if secret == nil {
|
||||
log.Fatal(errSecretKeyGeneration)
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if apiConfig.TLSEnabled {
|
||||
tlsConfig = newTLSConfig(apiConfig.TLSCACertPath, apiConfig.TLSCertPath, apiConfig.TLSKeyPath)
|
||||
|
@ -57,5 +97,6 @@ func newAPI(apiConfig apiConfig) *api {
|
|||
dataPath: apiConfig.DataPath,
|
||||
tlsConfig: tlsConfig,
|
||||
templatesURL: apiConfig.TemplatesURL,
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
|
88
api/auth.go
Normal file
88
api/auth.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
credentials struct {
|
||||
Username string `valid:"alphanum,required"`
|
||||
Password string `valid:"length(8)"`
|
||||
}
|
||||
authResponse struct {
|
||||
JWT string `json:"jwt"`
|
||||
}
|
||||
)
|
||||
|
||||
func hashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
func checkPasswordValidity(password string, hash string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
}
|
||||
|
||||
// authHandler defines a handler function used to authenticate users
|
||||
func (api *api) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.Header().Set("Allow", "POST")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var credentials credentials
|
||||
err = json.Unmarshal(body, &credentials)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse credentials", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = govalidator.ValidateStruct(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid credentials format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var username = credentials.Username
|
||||
var password = credentials.Password
|
||||
u, err := api.dataStore.getUserByUsername(username)
|
||||
if err != nil {
|
||||
log.Printf("User not found: %s", username)
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkPasswordValidity(password, u.Password)
|
||||
if err != nil {
|
||||
log.Printf("Invalid credentials for user: %s", username)
|
||||
http.Error(w, "Invalid credentials", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
token, err := api.generateJWTToken(username)
|
||||
if err != nil {
|
||||
log.Printf("Unable to generate JWT token: %s", err.Error())
|
||||
http.Error(w, "Unable to generate JWT token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := authResponse{
|
||||
JWT: token,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
98
api/datastore.go
Normal file
98
api/datastore.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
const (
|
||||
userBucketName = "users"
|
||||
)
|
||||
|
||||
type (
|
||||
dataStore struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
userItem struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
errUserNotFound = errors.New("User not found")
|
||||
)
|
||||
|
||||
func (dataStore *dataStore) initDataStore() error {
|
||||
return dataStore.db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(userBucketName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (dataStore *dataStore) cleanUp() {
|
||||
dataStore.db.Close()
|
||||
}
|
||||
|
||||
func newDataStore(databasePath string) (*dataStore, error) {
|
||||
db, err := bolt.Open(databasePath, 0600, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dataStore{
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dataStore *dataStore) getUserByUsername(username string) (*userItem, error) {
|
||||
var data []byte
|
||||
|
||||
err := dataStore.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(userBucketName))
|
||||
value := bucket.Get([]byte(username))
|
||||
if value == nil {
|
||||
return errUserNotFound
|
||||
}
|
||||
|
||||
data = make([]byte, len(value))
|
||||
copy(data, value)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var user userItem
|
||||
err = json.Unmarshal(data, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (dataStore *dataStore) updateUser(user userItem) error {
|
||||
buffer, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dataStore.db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(userBucketName))
|
||||
err = bucket.Put([]byte(user.Username), buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/net/websocket"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -12,21 +13,35 @@ import (
|
|||
// newHandler creates a new http.Handler with CSRF protection
|
||||
func (a *api) newHandler(settings *Settings) http.Handler {
|
||||
var (
|
||||
mux = http.NewServeMux()
|
||||
mux = mux.NewRouter()
|
||||
fileHandler = http.FileServer(http.Dir(a.assetPath))
|
||||
)
|
||||
|
||||
handler := a.newAPIHandler()
|
||||
|
||||
mux.Handle("/", fileHandler)
|
||||
mux.Handle("/dockerapi/", http.StripPrefix("/dockerapi", handler))
|
||||
mux.Handle("/ws/exec", websocket.Handler(a.execContainer))
|
||||
mux.HandleFunc("/auth", a.authHandler)
|
||||
mux.Handle("/users", addMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
a.usersHandler(w, r)
|
||||
}), a.authenticate, secureHeaders))
|
||||
mux.Handle("/users/{username}", addMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
a.userHandler(w, r)
|
||||
}), a.authenticate, secureHeaders))
|
||||
mux.Handle("/users/{username}/passwd", addMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
a.userPasswordHandler(w, r)
|
||||
}), a.authenticate, secureHeaders))
|
||||
mux.HandleFunc("/users/admin/check", a.checkAdminHandler)
|
||||
mux.HandleFunc("/users/admin/init", a.initAdminHandler)
|
||||
mux.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
settingsHandler(w, r, settings)
|
||||
})
|
||||
mux.HandleFunc("/templates", func(w http.ResponseWriter, r *http.Request) {
|
||||
templatesHandler(w, r, a.templatesURL)
|
||||
})
|
||||
// mux.PathPrefix("/dockerapi/").Handler(http.StripPrefix("/dockerapi", handler))
|
||||
mux.PathPrefix("/dockerapi/").Handler(http.StripPrefix("/dockerapi", addMiddleware(handler, a.authenticate, secureHeaders)))
|
||||
|
||||
mux.PathPrefix("/").Handler(http.StripPrefix("/", fileHandler))
|
||||
|
||||
// CSRF protection is disabled for the moment
|
||||
// CSRFHandler := newCSRFHandler(a.dataPath)
|
||||
// return CSRFHandler(newCSRFWrapper(mux))
|
||||
|
|
29
api/jwt.go
Normal file
29
api/jwt.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type claims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func (api *api) generateJWTToken(username string) (string, error) {
|
||||
expireToken := time.Now().Add(time.Hour * 8).Unix()
|
||||
claims := claims{
|
||||
username,
|
||||
jwt.StandardClaims{
|
||||
ExpiresAt: expireToken,
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
signedToken, err := token.SignedString(api.secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return signedToken, nil
|
||||
}
|
|
@ -4,14 +4,19 @@ import (
|
|||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version number of portainer API
|
||||
Version = "1.10.2"
|
||||
)
|
||||
|
||||
// main is the entry point of the program
|
||||
func main() {
|
||||
kingpin.Version("1.10.2")
|
||||
kingpin.Version(Version)
|
||||
var (
|
||||
endpoint = kingpin.Flag("host", "Dockerd endpoint").Default("unix:///var/run/docker.sock").Short('H').String()
|
||||
addr = kingpin.Flag("bind", "Address and port to serve Portainer").Default(":9000").Short('p').String()
|
||||
assets = kingpin.Flag("assets", "Path to the assets").Default(".").Short('a').String()
|
||||
data = kingpin.Flag("data", "Path to the data").Default(".").Short('d').String()
|
||||
data = kingpin.Flag("data", "Path to the folder where the data is stored").Default("/data").Short('d').String()
|
||||
tlsverify = kingpin.Flag("tlsverify", "TLS support").Default("false").Bool()
|
||||
tlscacert = kingpin.Flag("tlscacert", "Path to the CA").Default("/certs/ca.pem").String()
|
||||
tlscert = kingpin.Flag("tlscert", "Path to the TLS certificate file").Default("/certs/cert.pem").String()
|
||||
|
|
65
api/middleware.go
Normal file
65
api/middleware.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func addMiddleware(h http.Handler, middleware ...func(http.Handler) http.Handler) http.Handler {
|
||||
for _, mw := range middleware {
|
||||
h = mw(h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// authenticate provides Authentication middleware for handlers
|
||||
func (api *api) authenticate(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var token string
|
||||
|
||||
// Get token from the Authorization header
|
||||
// format: Authorization: Bearer
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
msg := fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
return nil, msg
|
||||
}
|
||||
return api.secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid JWT token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if parsedToken == nil || !parsedToken.Valid {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// context.Set(r, "user", parsedToken)
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// SecureHeaders adds secure headers to the API
|
||||
func secureHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Add("X-Frame-Options", "DENY")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
219
api/users.go
Normal file
219
api/users.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
passwordCheckRequest struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
passwordCheckResponse struct {
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
initAdminRequest struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
)
|
||||
|
||||
// handle /users
|
||||
// Allowed methods: POST
|
||||
func (api *api) usersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.Header().Set("Allow", "POST")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var user userItem
|
||||
err = json.Unmarshal(body, &user)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse user data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password, err = hashPassword(user.Password)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to hash user password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.dataStore.updateUser(user)
|
||||
if err != nil {
|
||||
log.Printf("Unable to persist user: %s", err.Error())
|
||||
http.Error(w, "Unable to persist user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle /users/admin/check
|
||||
// Allowed methods: POST
|
||||
func (api *api) checkAdminHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
w.Header().Set("Allow", "GET")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := api.dataStore.getUserByUsername("admin")
|
||||
if err == errUserNotFound {
|
||||
log.Printf("User not found: %s", "admin")
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Unable to retrieve user: %s", err.Error())
|
||||
http.Error(w, "Unable to retrieve user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = ""
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// handle /users/admin/init
|
||||
// Allowed methods: POST
|
||||
func (api *api) initAdminHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.Header().Set("Allow", "POST")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var requestData initAdminRequest
|
||||
err = json.Unmarshal(body, &requestData)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse user data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user := userItem{
|
||||
Username: "admin",
|
||||
}
|
||||
user.Password, err = hashPassword(requestData.Password)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to hash user password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.dataStore.updateUser(user)
|
||||
if err != nil {
|
||||
log.Printf("Unable to persist user: %s", err.Error())
|
||||
http.Error(w, "Unable to persist user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle /users/{username}
|
||||
// Allowed methods: PUT, GET
|
||||
func (api *api) userHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "PUT" {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var user userItem
|
||||
err = json.Unmarshal(body, &user)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse user data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password, err = hashPassword(user.Password)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to hash user password", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = api.dataStore.updateUser(user)
|
||||
if err != nil {
|
||||
log.Printf("Unable to persist user: %s", err.Error())
|
||||
http.Error(w, "Unable to persist user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else if r.Method == "GET" {
|
||||
vars := mux.Vars(r)
|
||||
username := vars["username"]
|
||||
|
||||
user, err := api.dataStore.getUserByUsername(username)
|
||||
if err == errUserNotFound {
|
||||
log.Printf("User not found: %s", username)
|
||||
http.Error(w, "User not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Unable to retrieve user: %s", err.Error())
|
||||
http.Error(w, "Unable to retrieve user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = ""
|
||||
json.NewEncoder(w).Encode(user)
|
||||
} else {
|
||||
w.Header().Set("Allow", "PUT, GET")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle /users/{username}/passwd
|
||||
// Allowed methods: POST
|
||||
func (api *api) userPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
w.Header().Set("Allow", "POST")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
username := vars["username"]
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var data passwordCheckRequest
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
http.Error(w, "Unable to parse user data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := api.dataStore.getUserByUsername(username)
|
||||
if err != nil {
|
||||
log.Printf("Unable to retrieve user: %s", err.Error())
|
||||
http.Error(w, "Unable to retrieve user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
valid := true
|
||||
err = checkPasswordValidity(data.Password, user.Password)
|
||||
if err != nil {
|
||||
valid = false
|
||||
}
|
||||
|
||||
response := passwordCheckResponse{
|
||||
Valid: valid,
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue