func (m *StrUserModel) SetUser(u kit.User) { if u == nil { m.User = nil m.UserId = "" } else { m.User = u.(*UserStrId) m.SetUserId(u.GetId()) } }
// Creating a session is equivalent to logging in. func (hooks SessionResourceHooks) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response { userService := res.Registry().UserService() meta := r.GetMeta() isAnonymous, _ := meta.Bool("anonymous") // Find user. userIdentifier := meta.String("user") adaptor := meta.String("adaptor") data, _ := meta.Map("authData") var user kit.User if !isAnonymous { if adaptor == "" { return kit.NewErrorResponse("adaptor_missing", "Expected 'adaptor' in metadata.", true) } if data == nil { kit.NewErrorResponse("no_or_invalid_auth_data", "Expected 'authData' dictionary in metadata.") } var err apperror.Error user, err = userService.AuthenticateUser(userIdentifier, adaptor, data) if err != nil { return kit.NewErrorResponse(err) } } session, err := userService.StartSession(user, r.GetFrontend()) if err != nil { return kit.NewErrorResponse(err) } responseMeta := make(map[string]interface{}) if !isAnonymous { userData, err := res.Backend().ModelToMap(user, true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } responseMeta["user"] = userData if user.GetProfile() != nil { profileData, err := res.Backend().ModelToMap(user.GetProfile(), true, false) if err != nil { return kit.NewErrorResponse("marshal_error", err) } responseMeta["profile"] = profileData } } return &kit.AppResponse{ Data: session, Meta: responseMeta, } }
func (UserResource) AllowFind(res kit.Resource, model kit.Model, user kit.User) bool { if user == nil { return false } if model.(kit.UserModel).GetUserId() == user.GetId() { return true } return user.HasRole("admin") }
func (m *IntUserModel) SetUser(u kit.User) { if u == nil { m.User = nil m.UserId = 0 } else { m.User = u.(*UserIntId) m.SetUserId(u.GetId()) } }
func (h *Service) AuthenticateUser(userIdentifier string, authAdaptorName string, data map[string]interface{}) (kit.User, apperror.Error) { authAdaptor := h.AuthAdaptor(authAdaptorName) if authAdaptor == nil { return nil, &apperror.Err{ Public: true, Code: "unknown_auth_adaptor", Message: "Unknown auth adaptor: " + authAdaptorName} } var user kit.User var err apperror.Error if userIdentifier != "" { user, err = h.FindUser(userIdentifier) if err != nil { return nil, err } else if user == nil { return nil, apperror.New("user_not_found", "Username/Email does not exist ", true) } } userId := "" if user != nil { userId = user.GetStrId() } userId, err = authAdaptor.Authenticate(userId, data) if err != nil { if err.IsPublic() { return nil, err } else { return nil, apperror.Wrap(err, "adaptor_error", true) } } if user == nil { // Query user to get a full user with permissions and profile. user, err = h.FindUser(userId) if err != nil { return nil, err } else if user == nil { return nil, &apperror.Err{ Code: "user_not_found", Message: fmt.Sprintf("User with id %v could not be found", userId), Public: true, } } } if !user.IsActive() { return nil, apperror.New("user_inactive", true) } return user, nil }
func (PageResource) AllowFind(res kit.Resource, obj kit.Model, user kit.User) bool { if p, ok := obj.(*PageIntId); ok && p.Published { return true } else if p, ok := obj.(*PageStrId); ok && p.Published { return true } u := obj.(kit.UserModel) return user != nil && (u.GetUserId() == user.GetId() || user.HasRole("admin")) }
func (a *AuthAdaptorPassword) RegisterUser(user kit.User, data map[string]interface{}) (kit.AuthItem, apperror.Error) { if data == nil { return nil, apperror.New("invalid_nil_data") } pw, _ := GetStringFromMap(data, "password") if pw == "" { return nil, apperror.New("invalid_data_no_password") } hash, err := bcrypt.GenerateFromPassword([]byte(pw), 10) if err != nil { return nil, apperror.Wrap(err, "hash_errr", "") } item := &AuthItemPassword{ Hash: string(hash), } item.SetId(user.GetId()) return item, nil }
func (s *Service) ChangePassword(user kit.User, newPassword string) apperror.Error { adaptor := s.AuthAdaptor("password") if adaptor == nil { return &apperror.Err{ Code: "no_password_adaptor", Message: "The UserService does not have the password auth adaptor", } } passwordAdaptor := adaptor.(*password.AuthAdaptorPassword) if err := passwordAdaptor.ChangePassword(user.GetStrId(), newPassword); err != nil { if err.IsPublic() { return err } else { return apperror.Wrap(err, "adapter_error") } return err } return nil }
func (hooks UserResourceHooks) AllowUpdate(res kit.Resource, obj kit.Model, old kit.Model, user kit.User) bool { if user == nil { return false } if user.HasRole("admin") || user.HasPermission("users.update") { return true } return obj.GetId() == user.GetId() }
func (UserResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool { if user == nil { return false } if obj.(kit.UserModel).GetUserId() == user.GetId() { return true } return user.HasRole("admin") || user.HasPermission(res.Collection()+".delete") }
func (res *Resource) Create(obj kit.Model, user kit.User) apperror.Error { if hook, ok := res.hooks.(CreateHook); ok { return hook.Create(res, obj, user) } // This has to be done before tthe AllowCreate hook to allow the hook to // compare UserId value. if userModel, ok := obj.(kit.UserModel); ok && user != nil { if reflector.R(userModel.GetUserId()).IsZero() { userModel.SetUserId(user.GetId()) } } if allowCreate, ok := res.hooks.(AllowCreateHook); ok { if !allowCreate.AllowCreate(res, obj, user) { return apperror.New("permission_denied") } } if beforeCreate, ok := res.hooks.(BeforeCreateHook); ok { if err := beforeCreate.BeforeCreate(res, obj, user); err != nil { return err } } if err := res.backend.Create(obj); err != nil { return err } if afterCreate, ok := res.hooks.(AfterCreateHook); ok { if err := afterCreate.AfterCreate(res, obj, user); err != nil { return err } } return nil }
func (s Service) StartSession(user kit.User, sessionType string) (kit.Session, apperror.Error) { token := randomToken() if token == "" { return nil, apperror.New("token_creation_failed") } session := s.Sessions.CreateModel().(kit.Session) session.SetType(sessionType) session.SetToken(token) session.SetStartedAt(time.Now()) session.SetValidUntil(time.Now().Add(time.Hour * 12)) if user != nil { session.SetUserId(user.GetId()) } err := s.Sessions.Create(session, nil) if err != nil { return nil, err } return session, nil }
func (h *Service) VerifySession(token string) (kit.User, kit.Session, apperror.Error) { rawSession, err := h.Sessions.FindOne(token) if err != nil { return nil, nil, err } else if rawSession == nil { return nil, nil, apperror.New("session_not_found", true) } session := rawSession.(kit.Session) if session.GetValidUntil().Sub(time.Now()) < 1 { return nil, nil, apperror.New("session_expired", true) } var user kit.User if !session.IsAnonymous() { // Load user. rawUser, err := h.FindUser(session.GetUserId()) if err != nil { return nil, nil, err } user = rawUser.(kit.User) if !user.IsActive() { return nil, nil, apperror.New("user_inactive", true) } } // Prolong session. session.SetValidUntil(time.Now().Add(time.Hour * 12)) if err := h.Sessions.Update(session, nil); err != nil { return nil, nil, err } return user, session, nil }
func (item *AuthItemOauth) SetUser(u kit.User) { item.SetUserId(u.GetId()) }
func (s *Service) SendPasswordResetEmail(user kit.User) apperror.Error { // Check that an email service is configured. mailService := s.registry.EmailService() if mailService == nil { return apperror.New("no_email_service") } hoursValid := 48 // Generate a token. expiresAt := time.Now().Add(time.Hour * time.Duration(hoursValid)) tokenItem, err := s.BuildToken("password_reset", user.GetStrId(), expiresAt) if err != nil { return err } token := tokenItem.GetToken() conf := s.registry.Config() // Build the confirmation url. url := conf.UString("url") if url == "" { return &apperror.Err{ Code: "no_url_set", Message: "Config must specify url", } } resetPath := conf.UString("users.passwordResetPath") if resetPath == "" { return &apperror.Err{ Code: "no_password_reset_path", Message: "Config must specify users.passwordResetPath", } } if !strings.Contains(resetPath, "{token}") { return &apperror.Err{ Code: "invalid_password_reset_path", Message: "users.passwordResetPath does not contain {token} placeholder", } } resetUrl := url + "/" + strings.Replace(resetPath, "{token}", token, -1) // Render email. subject := conf.UString("users.passwordResetSubject", "Password reset") var txtContent, htmlContent []byte txtTpl := conf.UString("users.passwordResetTextTpl") htmlTpl := conf.UString("users.passwordResetHtmlTpl") if txtTpl != "" && htmlTpl != "" { // Check that a template engine is configured. engine := s.registry.TemplateEngine() if engine == nil { return apperror.New("no_template_engine") } data := map[string]interface{}{ "user": user, "token": token, "hours_valid": hoursValid, } var err apperror.Error txtContent, err = s.registry.TemplateEngine().BuildFileAndRender(txtTpl, data) if err != nil { return apperror.Wrap(err, "password_reset_tpl_error", "Could not render password reset tpl") } htmlContent, err = s.registry.TemplateEngine().BuildFileAndRender(htmlTpl, data) if err != nil { return apperror.Wrap(err, "password_reset_tpl_error", "Could not render password reset tpl") } } else { tpl := `Password reset To reset your password, please visit %v. The link will be valid for %v hours. ` htmlTpl := `Password Reset<br><br> To reset your password, please visit <a href="%v">this link</a>.<br> The link will be valid for %v hours. ` txtContent = []byte(fmt.Sprintf(tpl, resetUrl, hoursValid)) htmlContent = []byte(fmt.Sprintf(htmlTpl, resetUrl, hoursValid)) } // Now build the email and send it. email := email.NewMail() email.SetSubject(subject) email.AddBody("text/plain", txtContent) email.AddBody("text/html", htmlContent) email.AddTo(user.GetEmail(), "") if err := mailService.Send(email); err != nil { return err } s.registry.Logger().WithFields(logrus.Fields{ "action": "users.password_reset_requested", "email": user.GetEmail(), "user_id": user.GetId(), "token": token, }).Debugf("Password reset email sent to %v for user %v", user.GetEmail(), user.GetId()) return nil }
func (AdminResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool { return user != nil && (user.HasRole("admin") || user.HasPermission(res.Collection()+".delete")) }
func (item *AuthItemPassword) SetUser(u kit.User) { item.SetUserId(u.GetId()) }
func (s *Service) SendConfirmationEmail(user kit.User) apperror.Error { // Check that an email service is configured. mailService := s.registry.EmailService() if mailService == nil { return apperror.New("no_email_service") } conf := s.registry.Config() // Check that sending is enabled. if !conf.UBool("users.sendEmailConfirmationEmail", true) { return nil } // Generate a token. tokenItem, err := s.BuildToken("email_confirmation", user.GetStrId(), time.Time{}) if err != nil { return err } token := tokenItem.GetToken() // Build the confirmation url. confirmationPath := conf.UString("users.emailConfirmationPath") if confirmationPath == "" { return &apperror.Err{ Code: "no_email_confirmation_path", Message: "Config must specify users.emailConfirmationPath", } } if !strings.Contains(confirmationPath, "{token}") { return &apperror.Err{ Code: "invalid_email_confirmation_path", Message: "users.emailConfirmationPath does not contain {token} placeholder", } } confirmationUrl := conf.UString("url") + "/" + strings.Replace(confirmationPath, "{token}", token, -1) // Render email. subject := conf.UString("users.emailConfirmationSubject", "Confirm your Email") var txtContent, htmlContent []byte txtTpl := conf.UString("users.emailConfirmationEmailTextTpl") htmlTpl := conf.UString("users.emailConfirmationEmailHtmlTpl") if txtTpl != "" && htmlTpl != "" { // Check that a template engine is configured. engine := s.registry.TemplateEngine() if engine == nil { return apperror.New("no_template_engine") } data := map[string]interface{}{ "user": user, "token": token, } var err apperror.Error txtContent, err = s.registry.TemplateEngine().BuildFileAndRender(txtTpl, data) if err != nil { return apperror.Wrap(err, "email_confirmation_tpl_error", "Could not render email confirmation tpl") } htmlContent, err = s.registry.TemplateEngine().BuildFileAndRender(htmlTpl, data) if err != nil { return apperror.Wrap(err, "email_confirmation_tpl_error", "Could not render email confirmation tpl") } } else { tpl := `Welcome to Appkit To confirm your email address, please visit %v. ` htmlTpl := `Welcome to Appkit<br><br> To confirm your email address, please visit <a href="%v">this link</a>. ` txtContent = []byte(fmt.Sprintf(tpl, confirmationUrl)) htmlContent = []byte(fmt.Sprintf(htmlTpl, confirmationUrl)) } // Now build the email and send it. email := email.NewMail() email.SetSubject(subject) email.AddBody("text/plain", txtContent) email.AddBody("text/html", htmlContent) email.AddTo(user.GetEmail(), "") if err := mailService.Send(email); err != nil { return err } s.registry.Logger().WithFields(logrus.Fields{ "action": "users.email_confirmation_mail_sent", "email": user.GetEmail(), "user_id": user.GetId(), "token": token, }).Debugf("Password reset email sent to %v for user %v", user.GetEmail(), user.GetId()) return nil }
// Restrict querying to admins. func (RoleResourceHooks) AllowFind(res kit.Resource, model kit.Model, user kit.User) bool { return user != nil && user.HasRole("admin") }
func (p IntIdUserProfile) SetUser(user kit.User) { p.SetUserId(user.GetId()) }
} func (c *Client) PostJson(path string, data string) (int, *Data, error) { return c.DoJson("POST", path, data) } var _ = Describe("App", func() { app := buildApp() registry := app.Registry() go func() { app.Run() }() time.Sleep(time.Millisecond * 500) client := NewClient("http://*****:*****@appkit.com"}}, "meta": { "adaptor": "password", "auth-data": {"password": "******"},
func (a *AuthAdaptorOauth) RegisterUser(user kit.User, data map[string]interface{}) (kit.AuthItem, apperror.Error) { serviceName, _ := GetStringFromMap(data, "service") if serviceName == "" { return nil, apperror.New("invalid_data_missing_service") } service := a.services[serviceName] if service == nil { return nil, &apperror.Err{ Code: "unconfigured_service", Message: fmt.Sprintf("The oauth service '%v' was not configured in oauth auth adaptor", serviceName), } } accessToken, _ := GetStringFromMap(data, "access_token") if accessToken == "" { return nil, apperror.New("invalid_data_missing_access_token") } // Exchange access token for long lived token. // This also verifies that the supplied token is valid. appToken, err := service.Exchange(accessToken) if err != nil { return nil, apperror.Wrap(err, "oauth_exchange_token_error", "") } userData, err := service.GetUserData(appToken) if err != nil { return nil, apperror.Wrap(err, "fetch_user_data_failed", "") } if userData.Id == "" { return nil, &apperror.Err{ Code: "fetched_userdata_missing_user_id", Message: "The userData fetched from the service does not contain a userId", } } item := &AuthItemOauth{ Service: serviceName, UserId: user.GetStrId(), ExternalUserId: userData.Id, Token: appToken, } item.Id = serviceName + "_" + userData.Id // Fill in user information. if user.GetEmail() == "" { if userData.Email != "" { user.SetEmail(userData.Email) user.SetIsEmailConfirmed(true) } else { return nil, &apperror.Err{ Code: "oauth_service_insufficient_data_error", Message: fmt.Sprintf("The oauth service %v did not supply the users email, which is required", serviceName), } } } if user.GetUsername() == "" && userData.Username != "" { user.SetUsername(userData.Username) } return item, nil }
func (s *Service) CreateUser(user kit.User, adaptorName string, authData map[string]interface{}) apperror.Error { adaptor := s.AuthAdaptor(adaptorName) if adaptor == nil { return &apperror.Err{ Code: "unknown_auth_adaptor", Message: fmt.Sprintf("Auth adaptor %v was not registered with user service", adaptorName), Public: true, } } authItem, err := adaptor.RegisterUser(user, authData) if err != nil { return apperror.Wrap(err, "adaptor_error", "") } if user.GetUsername() == "" { user.SetUsername(user.GetEmail()) } // Check if user with same username or email exists. oldUser, err2 := s.Users.Q(). Filter("email", user.GetEmail()).Or("username", user.GetUsername()).First() if err2 != nil { return err2 } else if oldUser != nil { return &apperror.Err{ Code: "user_exists", Message: "A user with the username or email already exists", Public: true, } } user.SetIsActive(true) profile := user.GetProfile() // If a profile is configured, and the user does not have a profile yet, // create a new one. if s.Profiles != nil && profile == nil { profile = s.Profiles.CreateModel().(kit.UserProfile) user.SetProfile(profile) } if err := s.Users.Create(user, nil); err != nil { return err } // Create profile if one exists. if profile != nil { profile.SetUser(user) if err := s.Profiles.Create(profile, user); err != nil { s.Users.Backend().Delete(user) return apperror.Wrap(err, "user_profile_create_error", "Could not create the user profile") } } // Persist auth item. if authItemUserId, ok := authItem.(kit.UserModel); ok { authItemUserId.SetUserId(user.GetId()) } if err := s.Users.Backend().Create(authItem); err != nil { s.Users.Backend().Delete(user) if profile != nil { s.Profiles.Backend().Delete(profile) } return apperror.Wrap(err, "auth_item_create_error", "") } if err := s.SendConfirmationEmail(user); err != nil { s.registry.Logger().Errorf("Could not send confirmation email: %v", err) } return nil }