Skip to content

Commit

Permalink
Merge pull request #36 from titouanfreville/issue-30-secureAPI
Browse files Browse the repository at this point in the history
Issue 30 secure api
  • Loading branch information
titouanfreville authored Mar 11, 2017
2 parents 5b9f1f7 + 971a5bb commit 3053652
Show file tree
Hide file tree
Showing 27 changed files with 2,540 additions and 268 deletions.
6 changes: 3 additions & 3 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ DEBUG=1
# ------------------------------
# MariaDB values ---------------
MYSQL_ROOT_PASSWORD=popcube_dev
MYSQL_DATABASE=popcube_test
MYSQL_USER=test_user
MYSQL_PASSWORD=test
MYSQL_DATABASE=${MYSQL_DATABASE:-popcube_dev}
MYSQL_USER=${MYSQL_USER:-dev_user}
MYSQL_PASSWORD=dev
# ------------------------------
################################
# RUNNING CONTAINER VARS #######
Expand Down
103 changes: 101 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log"
"net/http"
"os"
"regexp"

jwt "github.com/dgrijalva/jwt-go"
"github.com/jinzhu/gorm"
Expand Down Expand Up @@ -40,6 +41,7 @@ type key string
// }

var (
secret string
hmacSampleSecret []byte
tokenAuth *JwtAuth
userToken *jwt.Token
Expand All @@ -63,9 +65,23 @@ func newRandomString(length int) string {
return b.String()
}
func initAuth() {
secret := newRandomString(100)
hmacSampleSecret = []byte(secret)
tokenAuth = New("HS256", hmacSampleSecret, hmacSampleSecret)
claims := jwt.MapClaims{
"organisation_name": "PopCube",
"organisation_stack": 1,
"organisation_domain": "chat.popcube.xyz",
"public": false,
"owner": "TCMJJ",
"owner_mail": "[email protected]",
"owner_password": "popcube",
"type": "neworganisation",
"authorise": "this token let you create new organisation and a new user in an iner DB",
}
unsignedToken := *jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := unsignedToken.SignedString(hmacSampleSecret)
log.Print("Tolken new organisation && role : ")
log.Print(tokenString)
}

// createUserToken create JWT auth token for current login user
Expand Down Expand Up @@ -168,7 +184,19 @@ func basicRoutes(router *chi.Mux) {
// 404: incorrectIds
// 422: wrongEntity
// 503: databaseError
// default: genericError
router.Post("/login", loginMiddleware)
// swagger:route POST /initorganisation Init initOrganisation
//
// Try to log user in
//
// Login user with provided USERNAME && Password
//
// Responses:
// 200: initOk
// 503: databaseError
// default: genericError
router.Post("/initorganisation", initOrganisation)
router.Route("/publicuser", func(r chi.Router) {
// swagger:route POST /publicuser/new Users newPublicUser
//
Expand Down Expand Up @@ -258,16 +286,86 @@ func loginMiddleware(w http.ResponseWriter, r *http.Request) {

}

func initOrganisation(w http.ResponseWriter, r *http.Request) {
// Verify token
ctx := r.Context()
if jwtErr, ok := ctx.Value(jwtErrorKey).(error); ok {
if jwtErr != nil {
render.JSON(w, 401, jwtErr)
return
}
}
jwtToken, ok := ctx.Value(jwtTokenKey).(*jwt.Token)
if !ok || jwtToken == nil || !jwtToken.Valid {
render.JSON(w, 401, "token is not valid or does not exist")
return
}
tokenType, ok := jwtToken.Claims.(jwt.MapClaims)["type"]
if !ok {
render.JSON(w, 401, "Token is not valid. Type is undifined")
return
}
if tokenType != "neworganisation" {
render.JSON(w, 401, "Token is not an init organisation one")
return
}
// Token passed. Initialising organisation
store := datastores.Store()
db := dbStore.db
organisation := models.Organisation{
OrganisationName: jwtToken.Claims.(jwt.MapClaims)["organisation_name"].(string),
DockerStack: jwtToken.Claims.(jwt.MapClaims)["organisation_stack"].(int),
Domain: jwtToken.Claims.(jwt.MapClaims)["organisation_domain"].(string),
Public: jwtToken.Claims.(jwt.MapClaims)["public"].(bool),
}
user := models.User{
Username: jwtToken.Claims.(jwt.MapClaims)["owner"].(string),
Email: jwtToken.Claims.(jwt.MapClaims)["owner_mail"].(string),
Password: jwtToken.Claims.(jwt.MapClaims)["owner_password"].(string),
// Owner role should always have ID 1 as it is the first one created into the DB.
IDRole: 1,
}
if err := db.DB().Ping(); err != nil {
render.JSON(w, error503.StatusCode, error503)
return
}
appErr := store.Organisation().Save(&organisation, db)
if appErr != nil {
render.JSON(w, appErr.StatusCode, appErr)
return
}
appErr = store.User().Save(&user, db)
if appErr != nil {
render.JSON(w, appErr.StatusCode, appErr)
return
}
res := initOk{
Organisation: organisation,
Owner: user,
}
render.JSON(w, 201, res)
}

func newPublicUser(w http.ResponseWriter, r *http.Request) {
var data struct {
User *models.User
OmitID interface{} `json:"id,omitempty"`
}
store := datastores.Store()

db := dbStore.db
request := r.Body
err := chiRender.Bind(request, &data)
organisation := store.Organisation().Get(db)
allowedWebMails := store.AllowedWebMails().GetAll(db)
isAuthorizedMail := false
for _, authorizedMail := range allowedWebMails {
filter := "*" + authorizedMail.Domain
ok, _ := regexp.MatchString(filter, data.User.Email)
isAuthorizedMail = isAuthorizedMail || ok
}
if !isAuthorizedMail && !organisation.Public {
render.JSON(w, 401, "You can't sign up if organisation is not public or your email domain was unauthorized.")
}
if err != nil || data.User == nil {
render.JSON(w, error422.StatusCode, error422)
} else {
Expand All @@ -287,6 +385,7 @@ func newPublicUser(w http.ResponseWriter, r *http.Request) {
// StartAPI initialise the api with provided host and port.
func StartAPI(hostname string, port string, DbConnectionInfo *configs.DbConnection) {
router := newRouter()
_, _, secret = configs.InitConfig()
// Init DB connection
user := DbConnectionInfo.User
db := DbConnectionInfo.Database
Expand Down
7 changes: 7 additions & 0 deletions api/api_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,11 @@ type inviteToken struct {
Token string `json:"token"`
}

// swagger:parameters newPublicUser
type initToken struct {
// Init token you got when creating new organisation. Pass it as Authentication: bearer {{token}} in the header
// in:body
Token string `json:"token"`
}

// <><><><><> <><><><><> <><><><><> <><><><><> //
10 changes: 10 additions & 0 deletions api/api_responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ type inviteOk struct {
Token string `json:"token,omitempty"`
}

// initOk when init correctly proceed, return the organisation object and its owner.
//
// swagger:response loginOk
type initOk struct {
// in:body
Organisation models.Organisation `json:"organisation,omitempty"`
// in:body
Owner models.User `json:"user,omitempty"`
}

// ---------------------------------------------------
// Errors --------------------------------------------

Expand Down
11 changes: 9 additions & 2 deletions configs/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type APIServerInfo struct {
}

// InitConfig get configuration for project
func InitConfig() (DbConnection, APIServerInfo) {
func InitConfig() (DbConnection, APIServerInfo, string) {
// Default configurations
dbConnection := DbConnection{
User: "root",
Expand All @@ -34,11 +34,18 @@ func InitConfig() (DbConnection, APIServerInfo) {
Hostname: "",
Port: "3000",
}
// Dev secret
secret := "AAAAB3NzaC1yc2EAAAADAQABAAACAQCtdGt4uK8e1CEcTVZXSRJ9pRHxdeYBxq4oTh20DKH7exoikkEPbSAn34ZJPVVRdPMndg8Qg5xxHnwAtYvzYbxNWAxqYqvvvCKLJtjTS2dMeNLVz3FYD80MSJX3Tr5gpK7hHq9EEWB99onqMKDHlF3ZM3dBjwZH3mP7sWlqcdKc6lP9MGPsrpnXmBx3C4CSB7muMl8hF+4263gtS1oXHT0E16NFP3IgBNmvYavmOYSlqHs9NU7lZtNVbLbIZ2SCVrOJlcSKddvaMzIhXgRIK58VzbsqqaeVBTMrxrJopjLha2aTSe9luxOJZCf1foQKVf7eWPp4FK/zSSDMJbSX6+vsE1jFbuFF2dYmf8QW1UdDslZtQuCLzB4rqBmOiFx77DIyuZMMt5bjTi02nPYZL5Fo4vupcoV552QC6jyUG3nAoY28yPGmhKBb0EpbCd/qiroIAs5mXhaPGZriqq8DDRbqstHkubfXjDkZ6vWRDnCUfSioMky/bEC1X2KaMt/E0tpw8aWiIZXAble+CIWfo2HUj2GE/Y3Gf8f/A14Ec2E+Uz4xARcTL4UfopNU2P3Bxhz/KoIZFXYacKBphATsp+HB6sMKF5HJ+tn6mS0JFdgIpcClVMliap4zz6M92FOyyRW0wBHua6gOI+5nEMS2BDLBwTmw5otXOTFV8DaFNQzaiQ"
// Default host for DB in Docker containers
if os.Getenv("ENVTYPE") == "container" {
log.Print("<><><><> Setting host to container default \n")
dbConnection.Host = "database"
}

if newSecret := os.Getenv("POPCUBESECRET"); newSecret != "" {
secret = newSecret
}

// Get values set in env
if apiPort := os.Getenv("API_PORT"); apiPort != "" {
log.Print("<><><><> Setting api port \n")
Expand Down Expand Up @@ -73,5 +80,5 @@ func InitConfig() (DbConnection, APIServerInfo) {
}

// Return new configs
return dbConnection, APIServer
return dbConnection, APIServer, secret
}
97 changes: 97 additions & 0 deletions datastores/allowedWebMails_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package datastores

import (
"github.com/jinzhu/gorm"
"github.com/titouanfreville/popcubeapi/models"
u "github.com/titouanfreville/popcubeapi/utils"
)

// AllowedWebMailsStoreImpl Used to implement AllowedWebMailsStore interface
type AllowedWebMailsStoreImpl struct{}

// AllowedWebMails Generate the struct for allowedWebMails store
func (s StoreImpl) AllowedWebMails() AllowedWebMailsStore {
return AllowedWebMailsStoreImpl{}
}

// Save Use to save allowedWebMails in DB
func (asi AllowedWebMailsStoreImpl) Save(allowedWebMails *models.AllowedWebMails, db *gorm.DB) *u.AppError {
transaction := db.Begin()
if appError := allowedWebMails.IsValid(); appError != nil {
transaction.Rollback()
return u.NewAPIError(422, appError.ID, "Wrong data provided : \n ---- "+appError.Message+" ----")
}
if !transaction.NewRecord(&allowedWebMails) {
transaction.Rollback()
return u.NewAPIError(409, "duplicate entry", "You already authorized "+allowedWebMails.Domain+" mails to sign up.")
}
if err := transaction.Create(&allowedWebMails).Error; err != nil {
transaction.Rollback()
return u.NewAPIError(500, "unxepected error", "Unexpected error while adding entry. \n ---- "+err.Error()+"----")
}
transaction.Commit()
return nil
}

// Update Used to update allowedWebMails in DB
func (asi AllowedWebMailsStoreImpl) Update(allowedWebMails *models.AllowedWebMails, newAllowedWebMails *models.AllowedWebMails, db *gorm.DB) *u.AppError {
transaction := db.Begin()
if appError := allowedWebMails.IsValid(); appError != nil {
transaction.Rollback()
return u.NewAPIError(422, appError.ID, "Wrong data provided : \n ---- "+appError.Message+" ----")
}
// if appError := newAllowedWebMails.IsValid(); appError != nil {
// transaction.Rollback()
// return u.NewLocAppError("allowedWebMailsStoreImpl.Update.allowedWebMailsNew.PreSave", appError.ID, nil, appError.DetailedError)
// }
if err := transaction.Model(&allowedWebMails).Updates(&newAllowedWebMails).Error; err != nil {
transaction.Rollback()
return u.NewAPIError(500, "unxepected error", "Unexpected error while adding entry. \n ---- "+err.Error()+"----")
}
transaction.Commit()
return nil
}

// GetAll Used to get allowedWebMails from DB
func (asi AllowedWebMailsStoreImpl) GetAll(db *gorm.DB) []models.AllowedWebMails {
allowedWebMailss := []models.AllowedWebMails{}
db.Find(&allowedWebMailss)
return allowedWebMailss
}

// GetByID Used to get allowedWebMails from DB
func (asi AllowedWebMailsStoreImpl) GetByID(ID uint64, db *gorm.DB) models.AllowedWebMails {
allowedWebMails := models.AllowedWebMails{}
db.Where("idAllowedWebMails = ?", ID).First(&allowedWebMails)
return allowedWebMails
}

// GetByDomain Used to get allowedWebMails having providing domin
func (asi AllowedWebMailsStoreImpl) GetByDomain(domain string, db *gorm.DB) models.AllowedWebMails {
allowedWebMails := models.AllowedWebMails{}
db.Where("domain = ?", domain).First(&allowedWebMails)
return allowedWebMails
}

// GetByProvider Used to get allowedWebMails provided by ...
func (asi AllowedWebMailsStoreImpl) GetByProvider(provider string, db *gorm.DB) []models.AllowedWebMails {
allowedWebMails := []models.AllowedWebMails{}
db.Where("provider = ?", provider).First(&allowedWebMails)
return allowedWebMails

}

// Delete Used to remove specified allowedWebMails from DB
func (asi AllowedWebMailsStoreImpl) Delete(allowedWebMails *models.AllowedWebMails, db *gorm.DB) *u.AppError {
transaction := db.Begin()
if appError := allowedWebMails.IsValid(); appError != nil {
transaction.Rollback()
return u.NewLocAppError("allowedWebMailsStoreImpl.Delete.allowedWebMails.PreSave", appError.ID, nil, appError.DetailedError)
}
if err := transaction.Delete(&allowedWebMails).Error; err != nil {
transaction.Rollback()
return u.NewLocAppError("allowedWebMailsStoreImpl.Delete", "update.transaction.delete.encounterError :"+err.Error(), nil, "")
}
transaction.Commit()
return nil
}
Loading

0 comments on commit 3053652

Please sign in to comment.