github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/documize/api/endpoint/authentication_endpoint.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 package endpoint 13 14 import ( 15 "crypto/rand" 16 "database/sql" 17 "encoding/json" 18 "errors" 19 "fmt" 20 "net/http" 21 "strings" 22 "time" 23 24 jwt "github.com/dgrijalva/jwt-go" 25 26 "github.com/documize/community/documize/api/endpoint/models" 27 "github.com/documize/community/documize/api/entity" 28 "github.com/documize/community/documize/api/request" 29 "github.com/documize/community/documize/api/util" 30 "github.com/documize/community/documize/section/provider" 31 "github.com/documize/community/wordsmith/environment" 32 "github.com/documize/community/wordsmith/log" 33 "github.com/documize/community/wordsmith/utility" 34 ) 35 36 // Authenticate user based up HTTP Authorization header. 37 // An encrypted authentication token is issued with an expiry date. 38 func Authenticate(w http.ResponseWriter, r *http.Request) { 39 method := "Authenticate" 40 p := request.GetPersister(r) 41 42 authHeader := r.Header.Get("Authorization") 43 44 // check for http header 45 if len(authHeader) == 0 { 46 writeBadRequestError(w, method, "Missing Authorization header") 47 return 48 } 49 50 // decode what we received 51 data := strings.Replace(authHeader, "Basic ", "", 1) 52 decodedBytes, err := utility.DecodeBase64([]byte(data)) 53 54 if err != nil { 55 writeBadRequestError(w, method, "Unable to decode authentication token") 56 return 57 } 58 59 // check that we have domain:email:password (but allow for : in password field!) 60 decoded := string(decodedBytes) 61 credentials := strings.SplitN(decoded, ":", 3) 62 63 if len(credentials) != 3 { 64 writeBadRequestError(w, method, "Bad authentication token, expecting domain:email:password") 65 return 66 } 67 68 domain := strings.TrimSpace(strings.ToLower(credentials[0])) 69 domain = request.CheckDomain(domain) // TODO optimize by removing this once js allows empty domains 70 email := strings.TrimSpace(strings.ToLower(credentials[1])) 71 password := credentials[2] 72 log.Info("logon attempt for " + domain + " @ " + email) 73 74 user, err := p.GetUserByDomain(domain, email) 75 76 if err == sql.ErrNoRows { 77 writeUnauthorizedError(w) 78 return 79 } 80 81 if err != nil { 82 writeServerError(w, method, err) 83 return 84 } 85 86 if !user.Active || len(user.Reset) > 0 || len(user.Password) == 0 { 87 writeUnauthorizedError(w) 88 return 89 } 90 91 // Password correct and active user 92 if email != strings.TrimSpace(strings.ToLower(user.Email)) || !util.MatchPassword(user.Password, password, user.Salt) { 93 writeUnauthorizedError(w) 94 return 95 } 96 97 org, err := p.GetOrganizationByDomain(domain) 98 99 if err != nil { 100 writeUnauthorizedError(w) 101 return 102 } 103 104 // Attach user accounts and work out permissions 105 attachUserAccounts(p, org.RefID, &user) 106 107 if len(user.Accounts) == 0 { 108 writeUnauthorizedError(w) 109 return 110 } 111 112 authModel := models.AuthenticationModel{} 113 authModel.Token = generateJWT(user.RefID, org.RefID, domain) 114 authModel.User = user 115 116 json, err := json.Marshal(authModel) 117 118 if err != nil { 119 writeJSONMarshalError(w, method, "user", err) 120 return 121 } 122 123 writeSuccessBytes(w, json) 124 } 125 126 // Authorize secure API calls by inspecting authentication token. 127 // request.Context provides caller user information. 128 // Site meta sent back as HTTP custom headers. 129 func Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 130 method := "Authorize" 131 132 // Let certain requests pass straight through 133 authenticated := preAuthorizeStaticAssets(r) 134 135 if !authenticated { 136 token := findJWT(r) 137 hasToken := len(token) > 1 138 context, _, tokenErr := decodeJWT(token) 139 140 var org = entity.Organization{} 141 var err = errors.New("") 142 143 // We always grab the org record regardless of token status. 144 // Why? If bad token we might be OK to alow anonymous access 145 // depending upon the domain in question. 146 p := request.GetPersister(r) 147 148 if len(context.OrgID) == 0 { 149 org, err = p.GetOrganizationByDomain(request.GetRequestSubdomain(r)) 150 } else { 151 org, err = p.GetOrganization(context.OrgID) 152 } 153 154 context.Subdomain = org.Domain 155 156 // Inability to find org record spells the end of this request. 157 if err != nil { 158 writeForbiddenError(w) 159 return 160 } 161 162 // If we have bad auth token and the domain does not allow anon access 163 if !org.AllowAnonymousAccess && tokenErr != nil { 164 writeUnauthorizedError(w) 165 return 166 } 167 168 domain := request.GetSubdomainFromHost(r) 169 170 if org.Domain != domain { 171 writeUnauthorizedError(w) 172 return 173 } 174 175 // If we have bad auth token and the domain allows anon access 176 // then we generate guest context. 177 if org.AllowAnonymousAccess { 178 // So you have a bad token 179 if hasToken { 180 if tokenErr != nil { 181 writeUnauthorizedError(w) 182 return 183 } 184 } else { 185 // Just grant anon user guest access 186 context.UserID = "0" 187 context.OrgID = org.RefID 188 context.Authenticated = false 189 context.Guest = true 190 } 191 } 192 193 // Refresh context and persister 194 request.SetContext(r, context) 195 p = request.GetPersister(r) 196 197 context.AllowAnonymousAccess = org.AllowAnonymousAccess 198 context.OrgName = org.Title 199 context.Administrator = false 200 context.Editor = false 201 202 // Fetch user permissions for this org 203 if context.Authenticated { 204 user, err := getSecuredUser(p, org.RefID, context.UserID) 205 206 if err != nil { 207 writeServerError(w, method, err) 208 return 209 } 210 211 context.Administrator = user.Admin 212 context.Editor = user.Editor 213 } 214 215 request.SetContext(r, context) 216 p = request.GetPersister(r) 217 218 // Middleware moves on if we say 'yes' -- autheticated or allow anon access. 219 authenticated = context.Authenticated || org.AllowAnonymousAccess 220 } 221 222 if authenticated { 223 next(w, r) 224 } else { 225 w.WriteHeader(http.StatusUnauthorized) 226 } 227 } 228 229 // ValidateAuthToken checks the auth token and returns the corresponding user. 230 func ValidateAuthToken(w http.ResponseWriter, r *http.Request) { 231 232 // TODO should this go after token validation? 233 if s := r.URL.Query().Get("section"); s != "" { 234 if err := provider.Callback(s, w, r); err != nil { 235 log.Error("section validation failure", err) 236 w.WriteHeader(http.StatusUnauthorized) 237 } 238 return 239 } 240 241 method := "ValidateAuthToken" 242 243 context, claims, err := decodeJWT(findJWT(r)) 244 245 if err != nil { 246 log.Error("token validation", err) 247 w.WriteHeader(http.StatusUnauthorized) 248 return 249 } 250 251 request.SetContext(r, context) 252 p := request.GetPersister(r) 253 254 org, err := p.GetOrganization(context.OrgID) 255 256 if err != nil { 257 log.Error("token validation", err) 258 w.WriteHeader(http.StatusUnauthorized) 259 return 260 } 261 262 domain := request.GetSubdomainFromHost(r) 263 264 if org.Domain != domain || claims["domain"] != domain { 265 log.Error("token validation", err) 266 w.WriteHeader(http.StatusUnauthorized) 267 return 268 } 269 270 user, err := getSecuredUser(p, context.OrgID, context.UserID) 271 272 if err != nil { 273 log.Error("get user error for token validation", err) 274 w.WriteHeader(http.StatusUnauthorized) 275 return 276 } 277 278 json, err := json.Marshal(user) 279 280 if err != nil { 281 writeJSONMarshalError(w, method, "user", err) 282 return 283 } 284 285 writeSuccessBytes(w, json) 286 } 287 288 // Certain assets/URL do not require authentication. 289 // Just stops the log files being clogged up with failed auth errors. 290 func preAuthorizeStaticAssets(r *http.Request) bool { 291 if strings.ToLower(r.URL.Path) == "/" || 292 strings.ToLower(r.URL.Path) == "/validate" || 293 strings.ToLower(r.URL.Path) == "/favicon.ico" || 294 strings.ToLower(r.URL.Path) == "/robots.txt" || 295 strings.ToLower(r.URL.Path) == "/version" || 296 strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") { 297 298 return true 299 } 300 301 return false 302 } 303 304 var jwtKey string 305 306 func init() { 307 environment.GetString(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated", 308 func(t *string, n string) bool { 309 if jwtKey == "" { 310 b := make([]byte, 17) 311 _, err := rand.Read(b) 312 if err != nil { 313 jwtKey = err.Error() 314 log.Error("problem using crypto/rand", err) 315 return false 316 } 317 for k, v := range b { 318 if (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '0') { 319 b[k] = v 320 } else { 321 s := fmt.Sprintf("%x", v) 322 b[k] = s[0] 323 } 324 } 325 jwtKey = string(b) 326 log.Info("Please set DOCUMIZESALT or use -salt with this value: " + jwtKey) 327 } 328 return true 329 }) 330 } 331 332 // Generates JSON Web Token (http://jwt.io) 333 func generateJWT(user, org, domain string) string { 334 token := jwt.New(jwt.SigningMethodHS256) 335 336 // issuer 337 token.Claims["iss"] = "Documize" 338 // subject 339 token.Claims["sub"] = "webapp" 340 // expiry 341 token.Claims["exp"] = time.Now().Add(time.Hour * 168).Unix() 342 // data 343 token.Claims["user"] = user 344 token.Claims["org"] = org 345 token.Claims["domain"] = domain 346 347 tokenString, _ := token.SignedString([]byte(jwtKey)) 348 349 return tokenString 350 } 351 352 // Check for authorization token. 353 // We look for 'Authorization' request header OR query string "?token=XXX". 354 func findJWT(r *http.Request) (token string) { 355 header := r.Header.Get("Authorization") 356 357 if header != "" { 358 header = strings.Replace(header, "Bearer ", "", 1) 359 } 360 361 if len(header) > 1 { 362 token = header 363 } else { 364 query := r.URL.Query() 365 token = query.Get("token") 366 } 367 368 if token == "null" { 369 token = "" 370 } 371 372 return 373 } 374 375 // We take in raw token string and decode it. 376 func decodeJWT(tokenString string) (c request.Context, claims map[string]interface{}, err error) { 377 method := "decodeJWT" 378 379 // sensible defaults 380 c.UserID = "" 381 c.OrgID = "" 382 c.Authenticated = false 383 c.Guest = false 384 385 token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 386 return []byte(jwtKey), nil 387 }) 388 389 if err != nil { 390 err = fmt.Errorf("bad authorization token") 391 return 392 } 393 394 if !token.Valid { 395 if ve, ok := err.(*jwt.ValidationError); ok { 396 if ve.Errors&jwt.ValidationErrorMalformed != 0 { 397 log.Error("invalid token", err) 398 err = fmt.Errorf("bad token") 399 return 400 } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 401 log.Error("expired token", err) 402 err = fmt.Errorf("expired token") 403 return 404 } else { 405 log.Error("invalid token", err) 406 err = fmt.Errorf("bad token") 407 return 408 } 409 } else { 410 log.Error("invalid token", err) 411 err = fmt.Errorf("bad token") 412 return 413 } 414 } 415 416 c = request.NewContext() 417 c.UserID = token.Claims["user"].(string) 418 c.OrgID = token.Claims["org"].(string) 419 420 if len(c.UserID) == 0 || len(c.OrgID) == 0 { 421 err = fmt.Errorf("%s : unable parse token data", method) 422 return 423 } 424 425 c.Authenticated = true 426 c.Guest = false 427 428 return c, token.Claims, nil 429 }