Exemple #1
0
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)
}
Exemple #2
0
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
}
Exemple #3
0
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
}
Exemple #4
0
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)
}
Exemple #5
0
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
}
Exemple #6
0
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)
}