// POST /users/password/reset func HandlePasswordResetSend(context router.Context) error { // Find the user by email (if not found let them know) // Find the user by hex token in the db email := context.Param("email") user, err := users.First(users.Where("email=?", email)) if err != nil { return router.Redirect(context, "/users/password/reset?message=invalid_email") } // Generate a random token and url for the email token := auth.BytesToHex(auth.RandomToken()) // Generate the url to use in our email base := fmt.Sprintf("%s://%s", context.Request().URL.Scheme, context.Request().URL.Host) url := fmt.Sprintf("%s/users/password?token=%s", base, token) context.Logf("#info sending reset email:%s url:%s", email, url) // Update the user record with with this token p := map[string]string{"reset_token": token} user.Update(p) // Send a password reset email out //mail.Send("mymail") // Tell the user what we have done return router.Redirect(context, "/users/password/sent") }
// HandleHome displays a list of stories using gravity to order them // used for the home page for gravity rank see votes.go // responds to GET / func HandleHome(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Select only above 0 points, Order by rank, then points, then name q.Where("points > 0").Order("rank desc, points desc, id desc") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleDownload sends a single file func HandleDownload(context router.Context) error { // Find the file file, err := files.Find(context.ParamInt("id")) if err != nil { return router.InternalError(err) } // Authorise access to this file - only the file owners can access their own files err = authorise.Resource(context, file) if err != nil { return router.NotAuthorizedError(err) } // If we are permitted, send the file to the user //w.Header().Set("Content-Type", "text/plain; charset=utf-8") //http.DetectContentType(data []byte) string h := context.Header() h.Set("Content-Type", "application/pgp-encrypted") h.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file.Name())) h.Set("Content-Transfer-Encoding", "binary") http.ServeFile(context, context.Request(), file.Path) return nil }
// AuthenticityTokenFilter sets the authenticity token on the context and on the cookie func AuthenticityTokenFilter(c router.Context) error { token, err := auth.AuthenticityToken(c.Writer(), c.Request()) if err != nil { return err } c.Set(auth.SessionTokenKey, token) return nil }
// CreateAuthenticityToken returns an auth.AuthenticityToken and writes a secret to check it to the cookie func CreateAuthenticityToken(context router.Context) string { token, err := auth.AuthenticityToken(context.Writer(), context.Request()) if err != nil { context.Logf("#warn invalid authenticity token at %v", context) return "" // empty strings are invalid as tokens } return token }
func loginUser(context router.Context, user *users.User) error { // Now save the user details in a secure cookie, so that we remember the next request session, err := auth.Session(context, context.Request()) if err != nil { return err } context.Logf("#info Login success for user: %d %s", user.Id, user.Email) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) return nil }
// HandleIndex displays a list of stories at /stories func HandleIndex(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Order by date by default q.Where("points > -6").Order("created_at desc") // Filter if necessary - this assumes name and summary cols filter := context.Param("q") if len(filter) > 0 { // Replace special characters with escaped sequence filter = strings.Replace(filter, "_", "\\_", -1) filter = strings.Replace(filter, "%", "\\%", -1) wildcard := "%" + filter + "%" // Perform a wildcard search for name or url q.Where("stories.name ILIKE ? OR stories.url ILIKE ?", wildcard, wildcard) // If filtering, order by rank, not by date q.Order("rank desc, points desc, id desc") } // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// AuthenticityToken checks the token in the current request func AuthenticityToken(context router.Context) error { token := context.Param(auth.SessionTokenKey) err := auth.CheckAuthenticityToken(token, context.Request()) if err != nil { // If the check fails, log out the user and completely clear the session context.Logf("#warn invalid authenticity token at %v", context) session, err := auth.SessionGet(context.Request()) if err != nil { return err } session.Clear(context.Writer()) } return err }
// storiesXMLPath returns the xml path for a given request to a stories link func storiesXMLPath(context router.Context) string { request := context.Request() p := strings.Replace(request.URL.Path, ".xml", "", 1) if p == "/" { p = "/index" } q := request.URL.RawQuery if len(q) > 0 { q = "?" + q } return fmt.Sprintf("%s.xml%s", p, q) }
// HandleUpvoted displays a list of stories the user has upvoted in the past func HandleUpvoted(context router.Context) error { // Build a query q := stories.Query().Limit(listLimit) // Select only above 0 points, Order by rank, then points, then name q.Where("points > 0").Order("rank desc, points desc, id desc") // Select only stories which the user has upvoted user := authorise.CurrentUser(context) if !user.Anon() { // Can we use a join instead? v := query.New("votes", "story_id").Select("select story_id as id from votes").Where("user_id=? AND story_id IS NOT NULL AND points > 0", user.Id) storyIDs := v.ResultIDs() if len(storyIDs) > 0 { q.WhereIn("id", storyIDs) } } // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleCreate handles the POST of the create form for users func HandleCreate(context router.Context) error { // Authorise err := authorise.Resource(context, nil) if err != nil { return router.NotAuthorizedError(err) } // Setup context params, err := context.Params() if err != nil { return router.InternalError(err) } // Default to customer role etc - admins will have to promote afterwards params.Set("role", fmt.Sprintf("%d", users.RoleCustomer)) params.Set("status", fmt.Sprintf("%d", status.Published)) id, err := users.Create(params.Map()) if err != nil { return err } // Log creation context.Logf("#info Created user id,%d", id) // Redirect to the new user user, err := users.Find(id) if err != nil { return router.InternalError(err) } // Save the details in a secure cookie session, err := auth.Session(context, context.Request()) if err != nil { return router.InternalError(err) } context.Logf("#info CREATE for user: %d", user.Id) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) // Send them to their user profile page return router.Redirect(context, user.URLShow()) }
// Default file handler, used in development - in production serve with nginx func serveFile(context router.Context) error { // Assuming we're running from the root of the website localPath := "./public" + path.Clean(context.Path()) if _, err := os.Stat(localPath); err != nil { // If file not found return error if os.IsNotExist(err) { return router.NotFoundError(err) } // For other errors return not authorised return router.NotAuthorizedError(err) } // If the file exists and we can access it, serve it http.ServeFile(context, context.Request(), localPath) return nil }
// Handle serving assets in dev (if we can) - return true on success func serveAsset(context router.Context) error { p := path.Clean(context.Path()) // It must be under /assets, or we don't serve if !strings.HasPrefix(p, "/assets/") { return router.NotFoundError(nil) } // Try to find an asset in our list f := appAssets.File(path.Base(p)) if f == nil { return router.NotFoundError(nil) } localPath := "./" + f.LocalPath() http.ServeFile(context, context.Request(), localPath) return nil }
// HandleLogin handles a post to /users/login func HandleLogin(context router.Context) error { // Check we're not already logged in, if so redirect // Get the user details from the database params, err := context.Params() if err != nil { return router.NotFoundError(err) } // Need something neater than this - how best to do it? q := users.Where("email=?", params.Get("email")) user, err := users.First(q) if err != nil { context.Logf("#error Login failed for user no such user : %s %s", params.Get("email"), err) return router.Redirect(context, "/users/login?error=failed_email") } err = auth.CheckPassword(params.Get("password"), user.EncryptedPassword) if err != nil { context.Logf("#error Login failed for user : %s %s", params.Get("email"), err) return router.Redirect(context, "/users/login?error=failed_password") } // Now save the user details in a secure cookie, so that we remember the next request session, err := auth.Session(context, context.Request()) if err != nil { context.Logf("#error problem retrieving session") } context.Logf("#info Login success for user: %d %s", user.Id, user.Email) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) // Redirect to whatever page the user tried to visit before (if any) // For now send them to root return router.Redirect(context, "/") }
// HandleCode displays a list of stories linking to repos (github etc) using gravity to order them // responds to GET /stories/code func HandleCode(context router.Context) error { // Build a query q := stories.Query().Where("points > -6").Order("rank desc, points desc, id desc").Limit(listLimit) // Restrict to stories with have a url starting with github.com or bitbucket.org // other code repos can be added later q.Where("url ILIKE 'https://github.com%'").OrWhere("url ILIKE 'https://bitbucket.org'") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) // view.AddKey("tags", []string{"web", "mobile", "data", "email", "crypto", "data", "graphics", "ui", "security"}) // TODO: remove these calls and put in a filter // - given it is not too expensive, we could just generate tokens on every request view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// CurrentUser returns the saved user (or an empty anon user) for the current session cookie // Strictly speaking this should be authenticate.User func CurrentUser(context router.Context) *users.User { // First check if the user has already been set on context, if so return it if context.Get("current_user") != nil { return context.Get("current_user").(*users.User) } // Start with an anon user by default (role 0, id 0) user := &users.User{} // Build the session from the secure cookie, or create a new one session, err := auth.Session(context.Writer(), context.Request()) if err != nil { context.Logf("#error problem retrieving session") return user } // Fetch the current user record if we have one recorded in the session var id int64 ids := session.Get(auth.SessionUserKey) if len(ids) > 0 { id, err = strconv.ParseInt(ids, 10, 64) if err != nil { context.Logf("#error Error decoding session user key:%s\n", err) return user } } if id != 0 { u, err := users.Find(id) if err != nil { context.Logf("#info User not found from session id:%d\n", id) return user } user = u } return user }
// HandleCode displays a list of stories linking to repos (github etc) using gravity to order them // responds to GET /stories/code func HandleCode(context router.Context) error { // Build a query q := stories.Query().Where("points > -6").Order("rank desc, points desc, id desc").Limit(listLimit) // Restrict to stories with have a url starting with github.com or bitbucket.org // other code repos can be added later q.Where("url ILIKE 'https://github.com%'").OrWhere("url ILIKE 'https://bitbucket.org'") // Set the offset in pages if we have one page := int(context.ParamInt("page")) if page > 0 { q.Offset(listLimit * page) } // Fetch the stories results, err := stories.FindAll(q) if err != nil { return router.InternalError(err) } // Render the template view := view.New(context) setStoriesMetadata(view, context.Request()) view.AddKey("page", page) view.AddKey("stories", results) view.Template("stories/views/index.html.got") if context.Param("format") == ".xml" { view.Layout("") view.Template("stories/views/index.xml.got") } return view.Render() }
// HandleLogin handles a post to /users/login func HandleLogin(context router.Context) error { params, err := context.Params() if err != nil { return router.NotFoundError(err) } // Check users against their username - we could also check against the email later? name := params.Get("name") q := users.Where("name=?", name) user, err := users.FindFirst(q) if err != nil { context.Logf("#error Login failed for user : %s %s", name, err) return router.Redirect(context, "/users/login?error=failed_name") } err = auth.CheckPassword(params.Get("password"), user.Password) if err != nil { context.Logf("#error Login failed for user : %s %s", name, err) return router.Redirect(context, "/users/login?error=failed_password") } // Save the details in a secure cookie session, err := auth.Session(context, context.Request()) if err != nil { return router.InternalError(err) } context.Logf("#info LOGIN for user: %d", user.Id) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) // Send them to their user profile page return router.Redirect(context, user.URLShow()) }
// HandleSetup responds to a POST at /fragmenta/setup // by creating our first user and page func HandleSetup(context router.Context) error { // If we have pages or users already, do not proceed if !missingUsersAndPages() { return router.NotAuthorizedError(nil) } // Take the details given and create the first user params := map[string]string{ "email": context.Param("email"), "password": context.Param("password"), "name": nameFromEmail(context.Param("email")), "status": "100", "role": "100", "title": "Administrator", } uid, err := users.Create(params) if err != nil { return router.InternalError(err) } context.Logf("#info Created user #%d", uid) user, err := users.Find(uid) if err != nil { return router.InternalError(err) } // Login this user automatically - save cookie session, err := auth.Session(context, context.Request()) if err != nil { return router.InternalError(err) } context.Logf("#info Automatic login for first user: %d %s", user.Id, user.Email) session.Set(auth.SessionUserKey, fmt.Sprintf("%d", user.Id)) session.Save(context) // Load our welcomepage template html // and put it into the text field of a new page with id 1 welcomeText, err := ioutil.ReadFile("src/pages/views/welcome.html.got") if err != nil { return router.InternalError(err) } params = map[string]string{ "status": "100", "name": "Fragmenta", "url": "/", "text": string(welcomeText), } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } // Create another couple of simple pages as examples (about and privacy) params = map[string]string{ "status": "100", "name": "About Us", "url": "/about", "text": "<section class=\"narrow\"><h1>About us</h1><p>About us</p></section>", } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } params = map[string]string{ "status": "100", "name": "Privacy Policy", "url": "/privacy", "text": "<section class=\"narrow\"><h1>Privacy Policy</h1><p>We respect your privacy.</p></section>", } _, err = pages.Create(params) if err != nil { return router.InternalError(err) } // Redirect back to the newly populated home page return router.Redirect(context, "/") }