コード例 #1
0
ファイル: scope_test.go プロジェクト: pgpst/pgpst
func TestScope(t *testing.T) {
	Convey("Given a scope and a comparsion slice that matches it", t, func() {
		scope := []string{
			"hello",
			"world",
		}

		what := []string{
			"hello:world",
			"world",
		}

		Convey("InScope should return true", func() {
			So(models.InScope(scope, what), ShouldBeTrue)
		})
	})

	Convey("Given a scope and a comparsion slice that doesn't match it", t, func() {
		scope := []string{
			"hello",
			"world",
		}

		what := []string{
			"foobar",
		}

		Convey("InScope should return false", func() {
			So(models.InScope(scope, what), ShouldBeFalse)
		})
	})
}
コード例 #2
0
ファイル: routes_addresses.go プロジェクト: pgpst/pgpst
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
}
コード例 #3
0
ファイル: routes_resources.go プロジェクト: pgpst/pgpst
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)
}
コード例 #4
0
ファイル: routes_resources.go プロジェクト: pgpst/pgpst
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)
}
コード例 #5
0
ファイル: routes_resources.go プロジェクト: pgpst/pgpst
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
}
コード例 #6
0
ファイル: routes_tokens.go プロジェクト: pgpst/pgpst
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
}
コード例 #7
0
ファイル: routes_labels.go プロジェクト: pgpst/pgpst
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
}
コード例 #8
0
ファイル: routes_threads.go プロジェクト: pgpst/pgpst
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
}
コード例 #9
0
ファイル: routes_accounts.go プロジェクト: pgpst/pgpst
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)
}
コード例 #10
0
ファイル: routes_accounts.go プロジェクト: pgpst/pgpst
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)
}
コード例 #11
0
ファイル: routes_keys.go プロジェクト: pgpst/pgpst
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)
}