// Password will authenticate a person using the provided email and password. // A token will be generated if the authentication succeeds. func Password(render render.Render, account *doorbot.Account, r doorbot.Repositories, vm PasswordRequest) { personRepo := r.PersonRepository() authRepo := r.AuthenticationRepository() // Find the person by email person, err := personRepo.FindByEmail(r.DB(), vm.Authentication.Email) if err != nil { log.WithFields(log.Fields{ "error": err, "email": vm.Authentication.Email, "account_id": account.ID, "step": "find-person-by-email", }).Error("Api::Auth->Password database error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } if person == nil { log.WithFields(log.Fields{ "account_id": account.ID, "email": vm.Authentication.Email, "step": "find-person-by-email", }).Info("Api::Auth->Password invalid email.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } //Fetch the password authentication record authentication, err := authRepo.FindByPersonIDAndProviderID(r.DB(), person.ID, auth.ProviderPassword) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": person.ID, "step": "find-person-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } if authentication == nil { log.WithFields(log.Fields{ "account_id": account.ID, "person_id": person.ID, "step": "find-person-authentication", }).Info("Api::Auth->Password no authentication") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } //Compare the passwords err = security.PasswordCompare([]byte(authentication.Token), []byte(vm.Authentication.Password)) if err != nil { log.WithFields(log.Fields{ "error": err, "person_id": person.ID, "account_id": account.ID, "step": "compare-password", }).Info("Api::Auth->Password compare password error") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid email or password"})) return } // Find the first active API token for this person. token, err := authRepo.FindByPersonIDAndProviderID(r.DB(), person.ID, auth.ProviderAPIToken) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "person_id": person.ID, "step": "find-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } // No active token or the person has not yet signed in. Generate a new token. if token == nil { token = &doorbot.Authentication{ PersonID: person.ID, ProviderID: auth.ProviderAPIToken, Token: security.GenerateAPIToken(), } err = authRepo.Create(r.DB(), token) if err != nil { log.WithFields(log.Fields{ "error": err, "person_id": person.ID, "account_id": account.ID, "step": "save-authentication", }).Error("Api::Auth->Password database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } } log.WithFields(log.Fields{ "account_id": account.ID, "person_id": person.ID, }).Info("Api::Auth->Password user logged in") resp := APITokenResponse{} resp.Authentication.Token = fmt.Sprintf("%d.%s", person.ID, token.Token) resp.Person = person resp.Policy = getPolicyForPerson(person) render.JSON(http.StatusOK, resp) }
// AuthenticatePerson wraps the authentication logic for people func AuthenticatePerson(r doorbot.Repositories, token string) (*doorbot.Person, error) { ar := r.AuthenticationRepository() pr := r.PersonRepository() parts := strings.Split(token, ".") if len(parts) != 2 { log.WithFields(log.Fields{ "account_id": r.AccountScope(), "token": token, "parts_count": len(parts), }).Warn("Auth->AuthenticatePerson Invalid person token") return nil, nil } id, err := strconv.ParseUint(parts[0], 10, 32) if err != nil { return nil, nil } token = parts[1] authentication, err := ar.FindByProviderIDAndPersonIDAndToken(r.DB(), ProviderAPIToken, uint(id), token) if err != nil { log.WithFields(log.Fields{ "error": err, "step": "authentication-get-by-person-and-token", "person_id": id, "account_id": r.AccountScope(), "token": token, }).Error("Auth->AuthenticatePerson database error") return nil, err } if authentication == nil { log.WithFields(log.Fields{ "token": token, "person_id": id, "account_id": r.AccountScope(), }).Info("Auth->AuthenticatePerson authentication not found") return nil, nil } person, err := pr.Find(r.DB(), authentication.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": r.AccountScope(), "person_id": id, "authentication_id": authentication.ID, "step": "person-find", }).Error("Auth->AuthenticatePerson database error") return nil, err } if person == nil { log.WithFields(log.Fields{ "token": token, "person_id": id, "account_id": r.AccountScope(), "authentication_id": authentication.ID, }).Error("Auth->AuthenticatePerson person not found") return nil, nil } return person, err }
func Token(render render.Render, account *doorbot.Account, r doorbot.Repositories, vm TokenRequest) { var person *doorbot.Person var device *doorbot.Device var policy *security.Policy switch vm.Authentication.Type { case auth.AuthorizationPerson: authRepo := r.AuthenticationRepository() personRepo := r.PersonRepository() authentication, err := authRepo.FindByProviderIDAndToken(r.DB(), auth.ProviderAPIToken, vm.Authentication.Token) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-token", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if authentication == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-token", }).Info("Api::Auth->Token no token found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } person, err = personRepo.Find(r.DB(), authentication.PersonID) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-person", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if person == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Warning("Api::Auth->Token person not found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } policy = getPolicyForPerson(person) break case auth.AuthorizationDevice: deviceRepo := r.DeviceRepository() device, err := deviceRepo.FindByToken(r.DB(), vm.Authentication.Token) if err != nil { log.WithFields(log.Fields{ "error": err, "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Error("Api::Auth->Token database error") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } if device == nil { log.WithFields(log.Fields{ "account_id": account.ID, "token": vm.Authentication.Token, "step": "find-device", }).Info("Api::Auth->Token no device found.") render.JSON(http.StatusUnauthorized, doorbot.NewUnauthorizedErrorResponse([]string{"Invalid token"})) return } } resp := APITokenResponse{} resp.Authentication.Token = vm.Authentication.Token resp.Person = person resp.Policy = policy resp.Device = device render.JSON(http.StatusOK, resp) }
// Register a new account ( used by the dashboard ) func Register(render render.Render, config *doorbot.DoorbotConfig, r doorbot.Repositories, n notifications.Notificator, data RegisterViewModel) { repo := r.AccountRepository() tx, err := r.Transaction() if err != nil { log.WithFields(log.Fields{ "error": err, "step": "transaction-create", }).Error("Api::Accounts->Register database error.") render.Status(http.StatusInternalServerError) return } var host string if len(data.Account.Host) == 0 { host, err = generateHost(r, config) } else { var account *doorbot.Account account, err = repo.FindByHost(r.DB(), data.Account.Host) if account != nil { tx.Rollback() render.Status(http.StatusConflict) return } host = data.Account.Host } if err != nil { tx.Rollback() render.Status(http.StatusInternalServerError) return } if len(host) == 0 { log.WithFields(log.Fields{ "host": host, "step": "host-generation", }).Error("Api::Accounts->Register Unable to set a hostname.") tx.Rollback() render.Status(http.StatusServiceUnavailable) return } // Create the account instance account := &doorbot.Account{ Name: data.Account.Name, ContactName: data.Account.ContactName, ContactEmail: data.Account.ContactEmail, //TODO append the doorbot production domain Host: host, IsEnabled: true, } err = repo.Create(tx, account) if err != nil { tx.Rollback() log.WithFields(log.Fields{ "error": err, "host": host, "step": "account-create", }).Error("Api::Accounts->Register database error.") render.JSON(http.StatusInternalServerError, doorbot.NewInternalServerErrorResponse([]string{})) return } // Update the repositories account scopes to the one we just created. r.SetAccountScope(account.ID) // We need to create a person with a password to let them log in on the dashboard. person := &doorbot.Person{ Name: data.Account.ContactName, Email: data.Account.ContactEmail, AccountType: doorbot.AccountOwner, } ar := r.PersonRepository() err = ar.Create(tx, person) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-create", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } // Generate a random password password := security.RandomPassword(8) hash, err := security.PasswordCrypt([]byte(password)) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-password", }).Error("Api::Accounts->Register password generation error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } // Create a new authentication record for the user authr := r.AuthenticationRepository() authentication := &doorbot.Authentication{ PersonID: person.ID, ProviderID: auth.ProviderPassword, Token: string(hash), } err = authr.Create(tx, authentication) if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "person-authentication", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } err = tx.Commit() if err != nil { log.WithFields(log.Fields{ "error": err, "host": host, "email": person.Email, "step": "transaction-commit", }).Error("Api::Accounts->Register database error.") tx.Rollback() render.Status(http.StatusInternalServerError) return } //TODO Send an email to the user. log.WithFields(log.Fields{ "account_id": account.ID, "account_host": account.Host, "person_id": person.ID, }).Info("Account created") n.AccountCreated(account, person, password) render.JSON(http.StatusCreated, AccountViewModel{Account: account}) }