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) 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) 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) 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) 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) 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) }