func (a *API) authMiddleware(c *gin.Context) { // Get the "Authorization" header authorization := c.Request.Header.Get("Authorization") if authorization == "" { c.JSON(401, &gin.H{ "code": 401, "error": "Invalid Authorization header", }) c.Abort() return } // Split it into two parts - "Bearer" and token parts := strings.SplitN(authorization, " ", 2) if parts[0] != "Bearer" { c.JSON(401, &gin.H{ "code": 401, "error": "Invalid Authorization header", }) c.Abort() return } // Verify the token cursor, err := r.Table("tokens").Get(parts[1]).Do(func(token r.Term) map[string]interface{} { return map[string]interface{}{ "token": token, "account": r.Table("accounts").Get(token.Field("owner")), } }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) c.Abort() return } defer cursor.Close() var result struct { Token *models.Token `gorethink:"token"` Account *models.Account `gorethink:"account"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) c.Abort() return } // Validate the token if result.Token.Type != "auth" { c.JSON(401, &gin.H{ "code": 401, "error": "Invalid token type", }) c.Abort() return } // Check for expiration if result.Token.IsExpired() { c.JSON(401, &gin.H{ "code": 401, "error": "Your authentication token has expired", }) c.Abort() return } // Validate the account if result.Account.Status != "active" { c.JSON(401, &gin.H{ "code": 401, "error": "Your account is " + result.Account.Status, }) c.Abort() return } // Write token into environment c.Set("account", result.Account) c.Set("token", result.Token) // Write some headers into the response c.Header( "X-Authenticated-As", result.Account.ID+"; "+result.Account.MainAddress, ) c.Header( "X-Authenticated-Scope", strings.Join(result.Token.Scope, ", "), ) }
func (a *API) getAccountAddresses(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"addresses:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get addresses from database cursor, err := r.Table("addresses").GetAllByIndex("owner", id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var addresses []*models.Address if err := cursor.All(&addresses); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, addresses) return }
func (a *API) oauthToken(c *gin.Context) { // Decode the input var input struct { GrantType string `json:"grant_type"` Code string `json:"code"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` Address string `json:"address"` Password string `json:"password"` ExpiryTime int64 `json:"expiry_time"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": CodeGeneralInvalidInput, "message": err.Error(), }) return } // Switch the action switch input.GrantType { case "authorization_code": // Parameters: // - code - authorization code from the app // - client_id - id of the client app // - client_secret - secret of the client app // Fetch the application from database cursor, err := r.Table("applications").Get(input.ClientID).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } defer cursor.Close() var application *models.Application if err := cursor.One(&application); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } if application.ID == "" { c.JSON(422, &gin.H{ "code": CodeOAuthInvalidApplication, "message": "No such client ID.", }) return } if application.Secret != input.ClientSecret { c.JSON(422, &gin.H{ "code": CodeOAuthInvalidSecret, "message": "Invalid client secret.", }) return } // Fetch the code from the database cursor, err = r.Table("tokens").Get(input.Code).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } defer cursor.Close() var codeToken *models.Token if err := cursor.One(&codeToken); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } // Ensure token type and matching client id if codeToken.ID == "" || codeToken.Type != "code" || codeToken.ClientID != input.ClientID { c.JSON(422, &gin.H{ "code": CodeOAuthInvalidCode, "message": "Invalid code", }) return } // Create a new authentication code token := &models.Token{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: codeToken.Owner, ExpiryDate: codeToken.ExpiryDate, Type: "auth", Scope: codeToken.Scope, ClientID: input.ClientID, } // Remove code token if err := r.Table("tokens").Get(codeToken.ID).Delete().Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } // Insert it into database if err := r.Table("tokens").Insert(token).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } // Write the token into the response c.JSON(201, token) return case "password": // Parameters: // - address - address in the system // - password - sha256 of the account's password // - client_id - id of the client app used for stats // - expiry_time - seconds until token expires // If there's no domain, append default domain if strings.Index(input.Address, "@") == -1 { input.Address += "@" + a.Options.DefaultDomain } // Normalize the username na := utils.RemoveDots(utils.NormalizeAddress(input.Address)) // Validate input errors := []string{} if !govalidator.IsEmail(na) { errors = append(errors, "Invalid address format.") } var dp []byte if len(input.Password) != 64 { errors = append(errors, "Invalid password length.") } else { var err error dp, err = hex.DecodeString(input.Password) if err != nil { errors = append(errors, "Invalid password format.") } } if input.ExpiryTime == 0 { input.ExpiryTime = 86400 // 24 hours } else if input.ExpiryTime < 0 { errors = append(errors, "Invalid expiry time.") } if input.ClientID == "" { errors = append(errors, "Missing client ID.") } else { cursor, err := r.Table("applications").Get(input.ClientID).Ne(nil).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } defer cursor.Close() var appExists bool if err := cursor.One(&appExists); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } if !appExists { errors = append(errors, "Invalid client ID.") } } if len(errors) > 0 { c.JSON(422, &gin.H{ "code": CodeOAuthValidationFailed, "message": "Validation failed.", "errors": errors, }) return } // Fetch the address from the database cursor, err := r.Table("addresses").Get(na).Default(map[string]interface{}{}).Do(func(address r.Term) map[string]interface{} { return map[string]interface{}{ "address": address, "account": r.Branch( address.HasFields("id"), r.Table("accounts").Get(address.Field("owner")), nil, ), } }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } defer cursor.Close() var result struct { Address *models.Address `gorethink:"address"` Account *models.Account `gorethink:"account"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } // Verify that both address and account exist if result.Address == nil || result.Address.ID == "" || result.Account == nil || result.Account.ID == "" { c.JSON(401, &gin.H{ "code": CodeOAuthInvalidAddress, "message": "No such address exists", }) return } // Verify the password valid, update, err := result.Account.VerifyPassword(dp) if err != nil { c.JSON(500, &gin.H{ "code": CodeOAuthInvalidPassword, "message": err.Error(), }) return } if update { result.Account.DateModified = time.Now() if err := r.Table("accounts").Get(result.Account.ID).Update(result.Account).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } } if !valid { c.JSON(401, &gin.H{ "code": CodeOAuthInvalidPassword, "message": "Invalid password", }) return } // Create a new token token := &models.Token{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: result.Account.ID, ExpiryDate: time.Now().Add(time.Duration(input.ExpiryTime) * time.Second), Type: "auth", Scope: []string{"password_grant"}, ClientID: input.ClientID, } if result.Account.Subscription == "admin" { token.Scope = append(token.Scope, "admin") } // Insert it into database if err := r.Table("tokens").Insert(token).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": CodeGeneralDatabaseError, "message": err.Error(), }) return } // Write the token into the response c.JSON(201, token) return case "client_credentials": // Parameters: // - client_id - id of the application // - client_secret - secret of the application c.JSON(501, &gin.H{ "code": CodeGeneralUnimplemented, "message": "Client credentials flow is not implemented.", }) } // Same as default in the switch c.JSON(422, &gin.H{ "code": CodeGeneralInvalidAction, "message": "Validation failed", "errors": []string{"Invalid action"}, }) return }
func (a *API) hello(c *gin.Context) { c.JSON(http.StatusOK, &gin.H{ "name": version.String("pgpst-api"), }) }
func (a *API) readResource(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the resource ID and fetch it from database id := c.Param("id") cursor, err := r.Table("resources").Get(id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var resource *models.Resource if err := cursor.All(&resource); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if resource.Owner == account.ID { // Check the scope if !models.InScope(token.Scope, []string{"resources:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } c.JSON(200, resource) }
func (a *API) getAccountResources(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"resources:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // 1. Owner filter query := r.Table("resources").GetAllByIndex("owner", id) // 2. Tag filter if tagstr := c.Query("tags"); tagstr != "" { tags := strings.Split(tagstr, ",") // Cast to []interface{} tagsi := []interface{}{} for _, tag := range tags { tagsi = append(tagsi, tag) } query = query.Filter(func(row r.Term) r.Term { return row.Field("tags").Contains(tagsi...) }) } // 3. Meta filter // not needed right now // 4. Date created and date modified ts := func(field string) error { if dm := c.Query(field); dm != "" { dmp := strings.Split(dm, ",") if len(dmp) == 1 || dmp[1] == "" { // parse dmp[0] d0, err := time.Parse(time.RFC3339, dmp[0]) if err != nil { return err } // after dmp[0] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Ge(d0) }) } else { // parse dmp[1] d1, err := time.Parse(time.RFC3339, dmp[1]) if err != nil { return err } if dmp[0] == "" { // until dmp[1] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Le(d1) }) } else { // parse dmp[0] d0, err := time.Parse(time.RFC3339, dmp[0]) if err != nil { return err } // between dmp[0] and dmp[1] query = query.Filter(func(row r.Term) r.Term { return row.Field(field).Ge(d0).And(row.Field(field).Le(d1)) }) } } } return nil } if err := ts("date_modified"); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if err := ts("date_created"); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // 5. Pluck / Without if pls := c.Query("pluck"); pls != "" { pl := strings.Split(pls, ",") // Cast to []interface{} pli := []interface{}{} for _, field := range pl { pli = append(pli, field) } query = query.Pluck(pli...) } else if wos := c.Query("wo"); wos != "" { wo := strings.Split(wos, ",") // Cast to []interface{} woi := []interface{}{} for _, field := range wo { woi = append(woi, field) } query = query.Pluck(woi...) } // 6. Ordering if obs := c.Query("order_by"); obs != "" { ob := strings.Split(obs, ",") fields := []interface{}{} for _, fi := range ob { asc := true if fi[0] == '-' { asc = false fi = fi[1:] } else if fi[0] == '+' || fi[0] == ' ' { fi = fi[1:] } field := r.Row.Field(fi) if path := strings.Split(fi, "."); len(path) > 1 { field = r.Row.Field(path[0]) for i := 1; i < len(path); i++ { field = field.Field(path[i]) } } if !asc { field = r.Desc(field) } fields = append(fields, field) } query = query.OrderBy(fields...) } // 7. Limiting var ( sks = c.Query("skip") lms = c.Query("limit") err error skip, limit int ) if sks != "" { skip, err = strconv.Atoi(sks) if err != nil { c.JSON(400, &gin.H{ "code": 0, "error": err.Error(), }) return } } if lms != "" { limit, err = strconv.Atoi(lms) if err != nil { c.JSON(400, &gin.H{ "code": 0, "error": err.Error(), }) return } } if skip != 0 && limit != 0 { query = query.Slice(skip, skip+limit) } else if skip == 0 && limit != 0 { query = query.Limit(limit) } else if skip != 0 && limit == 0 { query = query.Skip(skip) } // Get resources from database without bodies cursor, err := query.Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var resources []*models.Resource if err := cursor.All(&resources); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, resources) return }
func (a *API) createToken(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) currentToken = c.MustGet("token").(*models.Token) ) if !models.InScope(currentToken.Scope, []string{"tokens:oauth"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Type string `json:"type"` ClientID string `json:"client_id"` Scope []string `json:"scope"` ExpiryTime int64 `json:"expiry_time"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Run the validation errors := []string{} // Type has to be code if input.Type != "code" && input.Type != "auth" { errors = append(errors, "Only \"code\" token can be created using this endpoint.") } // Scope must contain proper scopes sm := map[string]struct{}{} for _, scope := range input.Scope { if _, ok := models.Scopes[scope]; !ok { errors = append(errors, "Scope \""+scope+"\" does not exist.") } else { sm[scope] = struct{}{} } } if _, ok := sm["password_grant"]; ok { errors = append(errors, "You can not request the password grant scope.") } if _, ok := sm["admin"]; ok && account.Subscription != "admin" { errors = append(errors, "You can not request the admin scope.") } // Expiry time must be valid if input.ExpiryTime == 0 { input.ExpiryTime = 86400 } else if input.ExpiryTime < 0 { errors = append(errors, "Invalid expiry time.") } // Client ID has to be an application ID var application *models.Application if input.ClientID == "" { errors = append(errors, "Client ID is missing.") } else { cursor, err := r.Table("applications").Get(input.ClientID).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&application); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if application.ID == "" { errors = append(errors, "There is no such application.") } } // Abort the request if there are errors if len(errors) > 0 { c.JSON(422, &gin.H{ "code": 0, "message": "Validation failed.", "errors": errors, }) return } // Create a new token token := &models.Token{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, ExpiryDate: time.Now().Add(time.Duration(input.ExpiryTime) * time.Second), Type: input.Type, Scope: input.Scope, ClientID: input.ClientID, } // Insert it into database if err := r.Table("tokens").Insert(token).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Write the token into the response c.JSON(201, token) return }
func (a *API) createResource(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Check the scope if !models.InScope(token.Scope, []string{"resources:create"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Meta map[string]interface{} `json:"meta"` Body []byte `json:"body"` Tags []string `json:"tags"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Save it into database resource := &models.Resource{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, Meta: input.Meta, Body: input.Body, Tags: input.Tags, } // Insert it into database if err := r.Table("resources").Insert(resource).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } c.JSON(201, resource) }
func (a *API) getLabelThreads(c *gin.Context) { // Token and account from context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") // Get label from the database cursor, err := r.Table("labels").Get(id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } var label *models.Label if err := cursor.One(&label); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Check the ownership and scope if label.Owner == account.ID { if !models.InScope(token.Scope, []string{"labels:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get threads from the database cursor, err = r.Table("threads").GetAllByIndex("labels", label.ID).OrderBy(r.Desc("date_modified")).Map(func(thread r.Term) r.Term { return thread.Merge(map[string]interface{}{ "manifest": r.Table("emails").GetAllByIndex("thread", thread.Field("id")).OrderBy("date_modified").CoerceTo("array"), }).Do(func(thread r.Term) r.Term { return r.Branch( thread.Field("manifest").Count().Gt(0), thread.Merge(map[string]interface{}{ "manifest": thread.Field("manifest").Nth(0).Field("manifest"), }), thread.Without("manifest"), ) }) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } var threads []*extendedThread if err := cursor.All(&threads); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if threads == nil { threads = []*extendedThread{} } // Write the response c.JSON(200, threads) return }
func (a *API) getAccountLabels(c *gin.Context) { // Token and account from context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the ID from the URL id := c.Param("id") if id == "me" { id = ownAccount.ID } // Check the scope if id == ownAccount.ID { if !models.InScope(token.Scope, []string{"labels:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } else { if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } } // Get labels from database cursor, err := r.Table("labels").GetAllByIndex("owner", id).Map(func(label r.Term) r.Term { return label.Merge(map[string]interface{}{ "total_threads": r.Table("threads").GetAllByIndex("labels", label.Field("id")).Count(), "unread_threads": r.Table("threads").GetAllByIndex("labelsIsRead", []interface{}{ label.Field("id"), false, }).Count(), }) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var labels []struct { models.Label TotalThreads int `json:"total_threads" gorethink:"total_threads"` UnreadThreads int `json:"unread_threads" gorethink:"unread_threads"` } if err := cursor.All(&labels); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } // Write the response c.JSON(200, labels) return }
func (a *API) updateAccount(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Decode the input var input struct { MainAddress string `json:"main_address"` NewPassword []byte `json:"new_password"` OldPassword []byte `json:"old_password"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } var newAddress *models.Address if input.MainAddress != "" { // Fetch address from the database cursor, err := r.Table("addresses").Get(input.MainAddress).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&newAddress); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Verify that we got something if newAddress.ID == "" { c.JSON(422, &gin.H{ "code": 0, "message": "No such address exists", }) return } } // Resolve the account ID and check scope accordingly id := c.Param("id") if id == "me" { // Swap the ID to own account's ID id = account.ID } if id == account.ID { // Check the scope if !models.InScope(token.Scope, []string{"account:modify"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Validate password valid, _, err := account.VerifyPassword(input.OldPassword) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if !valid { c.JSON(401, &gin.H{ "code": 0, "message": "Invalid password", }) return } } else { // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Fetch the account from the database cursor, err := r.Table("accounts").Get(id).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() if err := cursor.One(&account); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Ensure that we got something if account.ID == "" { c.JSON(404, &gin.H{ "code": 0, "error": "Account not found", }) return } } // Apply change to the password if input.NewPassword != nil && len(input.NewPassword) != 0 { if len(input.NewPassword) == 32 { c.JSON(422, &gin.H{ "code": 0, "message": "Invalid new password format", }) return } if err := account.SetPassword(input.NewPassword); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } } // Apply change to the main address setting if newAddress.ID != "" { // Check address ownership if newAddress.Owner != account.ID { c.JSON(422, &gin.H{ "code": 0, "message": "User does not own that address", }) return } account.MainAddress = newAddress.ID } // Perform the update if err := r.Table("accounts").Update(account).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Write the response account.Password = nil c.JSON(200, account) }
func (a *API) readAccount(c *gin.Context) { // Get token and account info from the context var ( ownAccount = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Resolve the account ID and check scope accordingly id := c.Param("id") if id == "me" { // Swap the ID to own account's ID id = ownAccount.ID } if id == ownAccount.ID { // Check the scope if !models.InScope(token.Scope, []string{"account:read"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Get addresses from database cursor, err := r.Table("addresses").GetAllByIndex("owner", id).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var addresses []*models.Address if err := cursor.All(&addresses); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } ownAccount.Password = nil // Write the response c.JSON(200, struct { *models.Account Addresses []*models.Address `json:"addresses"` }{ Account: ownAccount, Addresses: addresses, }) return } // Check the scope if !models.InScope(token.Scope, []string{"admin"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Fetch the account cursor, err := r.Table("accounts").Get(id).Do(func(account r.Term) r.Term { return r.Branch( account.Eq(nil), map[string]interface{}{}, account.Without("password").Merge(map[string]interface{}{ "addresses": r.Table("addresses").GetAllByIndex("owner", account.Field("id")), }), ) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } defer cursor.Close() var result struct { models.Account Addresses []*models.Address `json:"addresses"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": 0, "error": err.Error(), }) return } if result.ID == "" { c.JSON(404, &gin.H{ "code": 0, "error": "Account not found", }) return } c.JSON(200, result) }
func (a *API) createAccount(c *gin.Context) { // Decode the input var input struct { Action string `json:"action"` Username string `json:"username"` AltEmail string `json:"alt_email"` Token string `json:"token"` Password string `json:"password"` Address string `json:"address"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Switch the action switch input.Action { case "reserve": // Parameters: // - username - desired username // - alt_email - desired email // Normalize the username styledID := utils.NormalizeUsername(input.Username) nu := utils.RemoveDots(styledID) // Validate input: // - len(username) >= 3 && len(username) <= 32 // - email.match(alt_email) errors := []string{} if len(nu) < 3 { errors = append(errors, "Username too short. It must be 3-32 characters long.") } if len(nu) > 32 { errors = append(errors, "Username too long. It must be 3-32 characters long.") } if !govalidator.IsEmail(input.AltEmail) { errors = append(errors, "Invalid alternative e-mail format.") } if len(errors) > 0 { c.JSON(422, &gin.H{ "code": 0, "message": "Validation failed.", "errors": errors, }) return } // Check in the database whether you can register such account cursor, err := r.Table("addresses").Get(nu + "@pgp.st").Ne(nil).Do(func(left r.Term) map[string]interface{} { return map[string]interface{}{ "username": left, "alt_email": r.Table("accounts").GetAllByIndex("alt_email", input.AltEmail).Count().Eq(1), } }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() var result struct { Username bool `gorethink:"username"` AltEmail bool `gorethink:"alt_email"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if result.Username || result.AltEmail { errors := []string{} if result.Username { errors = append(errors, "This username is taken.") } if result.AltEmail { errors = append(errors, "This email address is used.") } c.JSON(422, &gin.H{ "code": 0, "message": "Naming conflict", "errors": errors, }) return } // Create an account and an address address := &models.Address{ ID: nu + "@pgp.st", StyledID: styledID + "@pgp.st", DateCreated: time.Now(), Owner: "", // we set it later } account := &models.Account{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), MainAddress: address.ID, Subscription: "beta", AltEmail: input.AltEmail, Status: "inactive", } address.Owner = account.ID // Insert them into the database if err := r.Table("addresses").Insert(address).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if err := r.Table("accounts").Insert(account).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Write a response c.JSON(201, account) return case "activate": // Parameters: // - address - expected address // - token - relevant token for address errors := []string{} if !govalidator.IsEmail(input.Address) { errors = append(errors, "Invalid address format") } if input.Token == "" { errors = append(errors, "Token is missing") } if len(errors) > 0 { c.JSON(422, &gin.H{ "code": 0, "message": "Validation failed", "errors": errors, }) return } // Normalise input.Address input_address := utils.RemoveDots(utils.NormalizeAddress(input.Address)) // Check in the database whether these both exist cursor, err := r.Table("tokens").Get(input.Token).Do(func(left r.Term) r.Term { return r.Branch( left.Ne(nil).And(left.Field("type").Eq("activate")), map[string]interface{}{ "token": left, "account": r.Table("accounts").Get(left.Field("owner")), }, map[string]interface{}{ "token": nil, "account": nil, }, ) }).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() var result struct { Token *models.Token `gorethink:"token"` Account *models.Account `gorethink:"account"` } if err := cursor.One(&result); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } if result.Token == nil { c.JSON(422, &gin.H{ "code": 0, "message": "Activation failed", "errors": []string{"Invalid token"}, }) return } if result.Account.MainAddress != input_address { c.JSON(422, &gin.H{ "code": 0, "message": "Activation failed", "errors": []string{"Address does not match token"}, }) return } // Everything seems okay, let's go ahead and delete the token if err := r.Table("tokens").Get(result.Token.ID).Delete().Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // token has been deleted now, let's do some post deletion checks if result.Token.IsExpired() { errors = append(errors, "Token expired") } if result.Account.Status != "inactive" { errors = append(errors, "Account already active") } if len(errors) > 0 { c.JSON(422, &gin.H{ "code": 0, "message": "Activation failed", "errors": errors, }) return } // make the account active, welcome to pgp.st, new guy! if err := r.Table("accounts").Get(result.Account.ID).Update(map[string]interface{}{ "status": "active", "date_modified": time.Now(), }).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Create labels for that user if err := r.Table("labels").Insert([]*models.Label{ &models.Label{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: result.Account.ID, Name: "Inbox", System: true, }, &models.Label{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: result.Account.ID, Name: "Spam", System: true, }, &models.Label{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: result.Account.ID, Name: "Sent", System: true, }, &models.Label{ ID: uniuri.NewLen(uniuri.UUIDLen), DateCreated: time.Now(), DateModified: time.Now(), Owner: result.Account.ID, Name: "Starred", System: true, }, }).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Temporary response... c.JSON(201, &gin.H{ "id": result.Account.ID, "message": "Activation successful", }) return } // Same as default in the switch c.JSON(422, &gin.H{ "code": 0, "message": "Validation failed", "errors": []string{"Invalid action"}, }) return }
func (a *API) readKey(c *gin.Context) { // Resolve the ID param id := c.Param("id") if len(id) != 40 { // Look up by email cursor, err := r.Table("addresses").Get(id).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() var address *models.Address if err := cursor.One(&address); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Check if we've found that address if address.ID == "" { c.JSON(404, &gin.H{ "code": 0, "message": "Address not found", }) return } // Set the ID accordingly id = address.PublicKey } // Fetch the key from database cursor, err := r.Table("keys").Get(id).Default(map[string]interface{}{}).Run(a.Rethink) if err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } defer cursor.Close() var key *models.Key if err := cursor.One(&key); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } // Ensure that it exists, write the response if key.ID == "" { c.JSON(404, &gin.H{ "code": 0, "message": "Key not found", }) return } c.JSON(200, key) }
func (a *API) createKey(c *gin.Context) { // Get token and account info from the context var ( account = c.MustGet("account").(*models.Account) token = c.MustGet("token").(*models.Token) ) // Check the scope if !models.InScope(token.Scope, []string{"keys"}) { c.JSON(403, &gin.H{ "code": 0, "error": "Your token has insufficient scope", }) return } // Decode the input var input struct { Body []byte `json:"body"` } if err := c.Bind(&input); err != nil { c.JSON(422, &gin.H{ "code": 0, "message": err.Error(), }) return } // Validate the input if input.Body == nil || len(input.Body) == 0 { c.JSON(422, &gin.H{ "code": 0, "message": "No key body in the input", }) return } // Parse the key keyring, err := openpgp.ReadKeyRing(bytes.NewReader(input.Body)) if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Invalid key format", }) return } publicKey := keyring[0].PrimaryKey // Parse the identities identities := []*models.Identity{} for _, identity := range keyring[0].Identities { id := &models.Identity{ Name: identity.Name, } if identity.SelfSignature != nil { sig := identity.SelfSignature id.SelfSignature = &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, } } if identity.Signatures != nil { id.Signatures = []*models.Signature{} for _, sig := range identity.Signatures { id.Signatures = append(id.Signatures, &models.Signature{ Type: uint8(sig.SigType), Algorithm: uint8(sig.PubKeyAlgo), Hash: uint(sig.Hash), CreationTime: sig.CreationTime, SigLifetimeSecs: sig.SigLifetimeSecs, KeyLifetimeSecs: sig.KeyLifetimeSecs, IssuerKeyID: sig.IssuerKeyId, IsPrimaryID: sig.IsPrimaryId, RevocationReason: sig.RevocationReason, RevocationReasonText: sig.RevocationReasonText, }) } } identities = append(identities, id) } // Acquire key's length length, err := publicKey.BitLength() if err != nil { c.JSON(422, &gin.H{ "code": 0, "message": "Couldn't calculate bit length", }) return } // Generate a new key struct key := &models.Key{ ID: hex.EncodeToString(publicKey.Fingerprint[:]), DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, Algorithm: uint8(publicKey.PubKeyAlgo), Length: length, Body: input.Body, KeyID: publicKey.KeyId, KeyIDString: publicKey.KeyIdString(), KeyIDShortString: publicKey.KeyIdShortString(), Identities: identities, } // Insert it into database if err := r.Table("keys").Insert(key).Exec(a.Rethink); err != nil { c.JSON(500, &gin.H{ "code": 0, "message": err.Error(), }) return } c.JSON(201, key) }