func createUser(ctx context.Context, w http.ResponseWriter, r *http.Request) (status int, err error) { var body = struct { Address, Nick, Password, Company string }{} if err = json.NewDecoder(r.Body).Decode(&body); err != nil { return http.StatusBadRequest, err } var companyKey *datastore.Key if body.Company != "" { companyKey, err = datastore.DecodeKey(body.Company) if err != nil { return http.StatusBadRequest, err } } if err = util.CheckNick(body.Nick); err != nil { return http.StatusBadRequest, err } var address *mail.Address if address, err = mail.ParseAddress(body.Address); err != nil { return http.StatusBadRequest, err } // Duplicate length check. If we move this after the conflict checks, // we could end up returning with a short password after querying Datastore. // The other way round, we would have to hash the password, and then throw it // away because of possible conflicts. pw := []byte(body.Password) if err = password.CheckLen(pw); err != nil { return http.StatusBadRequest, err } var emailConflict bool if emailConflict, err = alreadyExists(ctx, "Address", address.Address); err != nil { return http.StatusInternalServerError, err } if emailConflict { return http.StatusConflict, errors.New("duplicate e-mail address") } var nickConflict bool if nickConflict, err = alreadyExists(ctx, "Nick", body.Nick); err != nil { return http.StatusInternalServerError, err } if nickConflict { return http.StatusConflict, errors.New("duplicate nick") } var hashedPassword []byte if hashedPassword, err = password.Hash(pw); err != nil { return http.StatusInternalServerError, err } user := model.User{ Address: *address, Nick: body.Nick, HashedPassword: hashedPassword, } var key *datastore.Key if companyKey == nil { key, err = user.Save(ctx) } else { // Bind user to company for eternity. key, err = user.SaveWithParent(ctx, companyKey) } if err != nil { return http.StatusInternalServerError, err } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(user.Key(key)) return http.StatusOK, nil }
// Invitation handles the creation of a new invitation and sends an e-mail to // the user. func Invitation(ctx context.Context, w http.ResponseWriter, r *http.Request) (status int, err error) { if r.Method != "POST" { return http.StatusMethodNotAllowed, nil } p, ok := passenger.FromContext(ctx) if !ok { return http.StatusUnauthorized, nil } cKey := p.UserKey.Parent() if cKey == nil { return http.StatusUnauthorized, nil } var company model.Company if err = datastore.Get(ctx, cKey, &company); err != nil { return http.StatusInternalServerError, err } var params = struct { Address, Challenge string }{} if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { return http.StatusBadRequest, err } address, err := mail.ParseAddress(params.Address) if err != nil { return http.StatusBadRequest, err } challengeKey, err := datastore.DecodeKey(params.Challenge) if err != nil { return http.StatusBadRequest, err } var challenge model.Challenge if err := datastore.Get(ctx, challengeKey, &challenge); err != nil { // TODO(flowlo): Actually look into err. If it is just something like // "not found", an internal server error is not appropriate. return http.StatusInternalServerError, err } // TODO(flowlo): Check whether the parent of the current user is the // parent of the challenge (if any), and check whether the challenge // even exists. var users model.Users keys, err := model.NewQueryForUser(). Filter("Address=", address.Address). Limit(1). GetAll(ctx, &users) if err != nil { return http.StatusInternalServerError, err } var key *datastore.Key var user model.User if len(keys) == 1 { key = keys[0] user = users[0] } else { user = model.User{Address: *address} key, err = user.Save(ctx) if err != nil { return http.StatusInternalServerError, err } profile := model.Profile{} if _, err = profile.SaveWithParent(ctx, key); err != nil { return http.StatusInternalServerError, err } } // NOTE: We are creating a new, orphaned Passenger here, because a // Passenger can only issue tokens for the encapsulated user. np := passenger.Passenger{ UserKey: key, } now := time.Now() token := &model.AccessToken{ Creation: now, Expiry: now.Add(time.Hour * 24 * 365), Description: "Initialization Token", } value, err := np.IssueToken(ctx, token) if err != nil { return http.StatusInternalServerError, err } query := base64.URLEncoding.EncodeToString([]byte(params.Challenge + ":" + value)) i := model.Invitation{ User: key, } buf := new(bytes.Buffer) if err = invitation.Execute(buf, struct { UserAddress, CompanyAddress mail.Address Token string }{ user.Address, company.Address, query, }); err != nil { return http.StatusInternalServerError, err } if err = appmail.Send(ctx, &appmail.Message{ Sender: "Lorenz Leutgeb <*****@*****.**>", To: []string{user.Address.String()}, Subject: "We challenge you!", Body: buf.String(), }); err != nil { return http.StatusInternalServerError, err } key, err = i.SaveWithParent(ctx, cKey) if err != nil { return http.StatusInternalServerError, err } json.NewEncoder(w).Encode(i.Key(key)) return http.StatusOK, nil }