Example #1
1
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, ", "),
	)
}
Example #2
0
func tokensList(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Get tokens from database
	cursor, err := r.Table("tokens").Map(func(row r.Term) r.Term {
		return r.Branch(
			row.HasFields("client_id"),
			row.Merge(map[string]interface{}{
				"owners_address": r.Table("accounts").Get(row.Field("owner")).Field("main_address"),
				"client_name":    r.Table("applications").Get(row.Field("client_id")).Field("name"),
			}),
			row.Merge(map[string]interface{}{
				"owners_address": r.Table("accounts").Get(row.Field("owner")).Field("main_address"),
			}),
		)
	}).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	var tokens []struct {
		models.Token
		OwnersAddress string `gorethink:"owners_address" json:"owners_address"`
		ClientName    string `gorethink:"client_name" json:"client_name,omitempty"`
	}
	if err := cursor.All(&tokens); err != nil {
		writeError(c, err)
		return 1
	}

	// Write the output
	if c.Bool("json") {
		if err := json.NewEncoder(c.App.Writer).Encode(tokens); err != nil {
			writeError(c, err)
			return 1
		}

		fmt.Fprint(c.App.Writer, "\n")
	} else {
		table := termtables.CreateTable()
		table.AddHeaders("id", "type", "owner", "client_name", "expired", "date_created")
		for _, token := range tokens {
			table.AddRow(
				token.ID,
				token.Type,
				token.OwnersAddress,
				token.ClientName,
				!token.ExpiryDate.IsZero() && token.ExpiryDate.Before(time.Now()),
				token.DateCreated.Format(time.RubyDate),
			)
		}
		fmt.Fprintln(c.App.Writer, table.Render())
	}

	return 0
}
Example #3
0
func applicationsList(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Get applications from database
	cursor, err := r.Table("applications").Map(func(row r.Term) r.Term {
		return row.Merge(map[string]interface{}{
			"owners_address": r.Table("accounts").Get(row.Field("owner")).Field("main_address"),
		})
	}).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	var applications []struct {
		models.Application
		OwnersAddress string `gorethink:"owners_address" json:"owners_address"`
	}
	if err := cursor.All(&applications); err != nil {
		writeError(c, err)
		return 1
	}

	// Write the output
	if c.Bool("json") {
		if err := json.NewEncoder(c.App.Writer).Encode(applications); err != nil {
			writeError(c, err)
			return 1
		}

		fmt.Fprint(c.App.Writer, "\n")
	} else {
		table := termtables.CreateTable()
		table.AddHeaders("id", "name", "owner", "homepage", "date_created")
		for _, application := range applications {
			table.AddRow(
				application.ID,
				application.Name,
				application.OwnersAddress,
				application.Homepage,
				application.DateCreated.Format(time.RubyDate),
			)
		}
		fmt.Fprintln(c.App.Writer, table.Render())
	}

	return 0
}
Example #4
0
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
}
Example #5
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)
}
Example #6
0
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)
}
Example #7
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)
}
Example #8
0
func applicationsAdd(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Input struct
	var input struct {
		Owner       string `json:"owner"`
		Callback    string `json:"callback"`
		Homepage    string `json:"homepage"`
		Name        string `json:"name"`
		Description string `json:"description"`
	}

	// Read JSON from stdin
	if c.Bool("json") {
		if err := json.NewDecoder(c.App.Env["reader"].(io.Reader)).Decode(&input); err != nil {
			writeError(c, err)
			return 1
		}
	} else {
		// Buffer stdin
		rd := bufio.NewReader(c.App.Env["reader"].(io.Reader))
		var err error

		// Acquire from interactive input
		fmt.Fprint(c.App.Writer, "Owner's ID: ")
		input.Owner, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Owner = strings.TrimSpace(input.Owner)

		fmt.Fprint(c.App.Writer, "Application's name: ")
		input.Name, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Name = strings.TrimSpace(input.Name)

		fmt.Fprint(c.App.Writer, "Homepage URL: ")
		input.Homepage, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Homepage = strings.TrimSpace(input.Homepage)

		fmt.Fprint(c.App.Writer, "Description: ")
		input.Description, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Description = strings.TrimSpace(input.Description)

		fmt.Fprint(c.App.Writer, "Callback URL: ")
		input.Callback, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Callback = strings.TrimSpace(input.Callback)
	}

	// Validate the input

	// Check if account ID exists
	cursor, err := r.Table("accounts").Get(input.Owner).Ne(nil).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	defer cursor.Close()
	var exists bool
	if err := cursor.One(&exists); err != nil {
		writeError(c, err)
		return 1
	}
	if !exists {
		writeError(c, fmt.Errorf("Account %s doesn't exist", input.Owner))
		return 1
	}

	// Homepage URL should be a URL
	if !govalidator.IsURL(input.Homepage) {
		writeError(c, fmt.Errorf("%s is not a URL", input.Homepage))
		return 1
	}

	// Callback URL should be a URL
	if !govalidator.IsURL(input.Callback) {
		writeError(c, fmt.Errorf("%s is not a URL", input.Callback))
		return 1
	}

	// Insert into database
	application := &models.Application{
		ID:           uniuri.NewLen(uniuri.UUIDLen),
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        input.Owner,
		Secret:       uniuri.NewLen(32),
		Callback:     input.Callback,
		Homepage:     input.Homepage,
		Name:         input.Name,
		Description:  input.Description,
	}

	if !c.GlobalBool("dry") {
		if err := r.Table("applications").Insert(application).Exec(session); err != nil {
			writeError(c, err)
			return 1
		}
	}

	// Write a success message
	fmt.Fprintf(c.App.Writer, "Created a new application with ID %s\n", application.ID)
	return 0
}
Example #9
0
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)
}
Example #10
0
func tokensAdd(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Input struct
	var input struct {
		Owner      string    `json:"owner"`
		ExpiryDate time.Time `json:"expiry_date"`
		Type       string    `json:"type"`
		Scope      []string  `json:"scope"`
		ClientID   string    `json:"client_id"`
	}

	// Read JSON from stdin
	if c.Bool("json") {
		if err := json.NewDecoder(c.App.Env["reader"].(io.Reader)).Decode(&input); err != nil {
			writeError(c, err)
			return 1
		}
	} else {
		// Buffer stdin
		rd := bufio.NewReader(c.App.Env["reader"].(io.Reader))
		var err error

		// Acquire from interactive input
		fmt.Fprint(c.App.Writer, "Owner's ID: ")
		input.Owner, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Owner = strings.TrimSpace(input.Owner)

		fmt.Fprint(c.App.Writer, "Type [auth/activate/code]: ")
		input.Type, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Type = strings.TrimSpace(input.Type)

		fmt.Fprint(c.App.Writer, "Expiry date [2006-01-02T15:04:05Z07:00/empty]: ")
		expiryDate, err := rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		expiryDate = strings.TrimSpace(expiryDate)
		if expiryDate != "" {
			input.ExpiryDate, err = time.Parse(time.RFC3339, expiryDate)
			if err != nil {
				writeError(c, err)
				return 1
			}
		}

		if input.Type == "auth" || input.Type == "code" {
			fmt.Fprint(c.App.Writer, "Client ID: ")
			input.ClientID, err = rd.ReadString('\n')
			if err != nil {
				writeError(c, err)
				return 1
			}
			input.ClientID = strings.TrimSpace(input.ClientID)

			fmt.Fprint(c.App.Writer, "Scope (seperated by commas): ")
			scope, err := rd.ReadString('\n')
			if err != nil {
				writeError(c, err)
				return 1
			}
			scope = strings.TrimSpace(scope)
			input.Scope = strings.Split(scope, ",")
		}
	}

	// Validate the input

	// Type has to be either auth or activate
	if input.Type != "auth" && input.Type != "activate" && input.Type != "code" {
		writeError(c, fmt.Errorf("Token type must be either auth or activate. Got %s.", input.Type))
		return 1
	}

	// Scopes must exist
	if input.Scope != nil && len(input.Scope) > 0 {
		for _, scope := range input.Scope {
			if _, ok := models.Scopes[scope]; !ok {
				writeError(c, fmt.Errorf("Scope %s doesn't exist", scope))
				return 1
			}
		}
	}

	// Owner must exist
	cursor, err := r.Table("accounts").Get(input.Owner).Ne(nil).Run(session)
	if err != nil {
		writeError(c, err)
	}
	defer cursor.Close()
	var exists bool
	if err := cursor.One(&exists); err != nil {
		writeError(c, err)
		return 1
	}
	if !exists {
		writeError(c, fmt.Errorf("Account %s doesn't exist", input.Owner))
		return 1
	}

	// Application must exist
	if input.ClientID != "" {
		cursor, err = r.Table("applications").Get(input.ClientID).Ne(nil).Run(session)
		if err != nil {
			writeError(c, err)
		}
		defer cursor.Close()
		var exists bool
		if err := cursor.One(&exists); err != nil {
			writeError(c, err)
			return 1
		}
		if !exists {
			writeError(c, fmt.Errorf("Application %s doesn't exist", input.ClientID))
			return 1
		}
	}

	// Insert into database
	token := &models.Token{
		ID:           uniuri.NewLen(uniuri.UUIDLen),
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        input.Owner,
		ExpiryDate:   input.ExpiryDate,
		Type:         input.Type,
		Scope:        input.Scope,
		ClientID:     input.ClientID,
	}

	if !c.GlobalBool("dry") {
		if err := r.Table("tokens").Insert(token).Exec(session); err != nil {
			writeError(c, err)
			return 1
		}
	}

	// Write a success message
	fmt.Fprintf(c.App.Writer, "Created a new %s token with ID %s\n", token.Type, token.ID)
	return 0
}
Example #11
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
}
Example #12
0
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)
}
Example #13
0
func accountsAdd(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Input struct
	var input struct {
		MainAddress  string `json:"main_address"`
		Password     string `json:"password"`
		Subscription string `json:"subscription"`
		AltEmail     string `json:"alt_email"`
		Status       string `json:"status"`
	}

	// Read JSON from stdin
	if c.Bool("json") {
		if err := json.NewDecoder(c.App.Env["reader"].(io.Reader)).Decode(&input); err != nil {
			writeError(c, err)
			return 1
		}
	} else {
		// Buffer stdin
		rd := bufio.NewReader(c.App.Env["reader"].(io.Reader))
		var err error

		// Acquire from interactive input
		fmt.Fprint(c.App.Writer, "Main address: ")
		input.MainAddress, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.MainAddress = strings.TrimSpace(input.MainAddress)

		fmt.Fprint(c.App.Writer, "Password: "******"Password: "******"Subscription [beta/admin]: ")
		input.Subscription, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Subscription = strings.TrimSpace(input.Subscription)

		fmt.Fprint(c.App.Writer, "Alternative address: ")
		input.AltEmail, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.AltEmail = strings.TrimSpace(input.AltEmail)

		fmt.Fprint(c.App.Writer, "Status [inactive/active/suspended]: ")
		input.Status, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Status = strings.TrimSpace(input.Status)
	}

	// Analyze the input

	// First of all, the address. Append domain if it has no such suffix.
	if strings.Index(input.MainAddress, "@") == -1 {
		input.MainAddress += "@" + c.GlobalString("default_domain")
	}

	// And format it
	styledID := utils.NormalizeAddress(input.MainAddress)
	input.MainAddress = utils.RemoveDots(styledID)

	// Then check if it's taken.
	cursor, err := r.Table("addresses").Get(input.MainAddress).Ne(nil).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	defer cursor.Close()
	var taken bool
	if err := cursor.One(&taken); err != nil {
		writeError(c, err)
		return 1
	}
	if taken {
		writeError(c, fmt.Errorf("Address %s is already taken", input.MainAddress))
		return 1
	}

	// If the password isn't 64 characters long, then hash it.
	var password []byte
	if len(input.Password) != 64 {
		hash := sha256.Sum256([]byte(input.Password))
		password = hash[:]
	} else {
		password, err = hex.DecodeString(input.Password)
		if err != nil {
			writeError(c, err)
			return 1
		}
	}

	// Subscription has to be beta or admin
	if input.Subscription != "beta" && input.Subscription != "admin" {
		writeError(c, fmt.Errorf("Subscription has to be either beta or admin. Got %s.", input.Subscription))
		return 1
	}

	// AltEmail must be an email
	if !govalidator.IsEmail(input.AltEmail) {
		writeError(c, fmt.Errorf("Email %s has an incorrect format", input.AltEmail))
		return 1
	}

	// Status has to be inactive/active/suspended
	if input.Status != "inactive" && input.Status != "active" && input.Status != "suspended" {
		writeError(c, fmt.Errorf("Status has to be either inactive, active or suspended. Got %s.", input.Status))
		return 1
	}

	// Prepare structs to insert
	account := &models.Account{
		ID:           uniuri.NewLen(uniuri.UUIDLen),
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		MainAddress:  input.MainAddress,
		Subscription: input.Subscription,
		AltEmail:     input.AltEmail,
		Status:       input.Status,
	}
	if err := account.SetPassword([]byte(password)); err != nil {
		writeError(c, err)
		return 1
	}

	address := &models.Address{
		ID:           input.MainAddress,
		StyledID:     styledID,
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        account.ID,
	}

	var labels []*models.Label
	if account.Status != "inactive" {
		labels = []*models.Label{
			&models.Label{
				ID:           uniuri.NewLen(uniuri.UUIDLen),
				DateCreated:  time.Now(),
				DateModified: time.Now(),
				Owner:        account.ID,
				Name:         "Inbox",
				System:       true,
			},
			&models.Label{
				ID:           uniuri.NewLen(uniuri.UUIDLen),
				DateCreated:  time.Now(),
				DateModified: time.Now(),
				Owner:        account.ID,
				Name:         "Spam",
				System:       true,
			},
			&models.Label{
				ID:           uniuri.NewLen(uniuri.UUIDLen),
				DateCreated:  time.Now(),
				DateModified: time.Now(),
				Owner:        account.ID,
				Name:         "Sent",
				System:       true,
			},
			&models.Label{
				ID:           uniuri.NewLen(uniuri.UUIDLen),
				DateCreated:  time.Now(),
				DateModified: time.Now(),
				Owner:        account.ID,
				Name:         "Starred",
				System:       true,
			},
		}
	}

	// Insert them into database
	if !c.Bool("dry") {
		if err := r.Table("addresses").Insert(address).Exec(session); err != nil {
			writeError(c, err)
			return 1
		}
		if err := r.Table("accounts").Insert(account).Exec(session); err != nil {
			writeError(c, err)
			return 1
		}
		if labels != nil {
			if err := r.Table("labels").Insert(labels).Exec(session); err != nil {
				writeError(c, err)
				return 1
			}
		}
	}

	// Write a success message
	fmt.Fprintf(c.App.Writer, "Created a new account with ID %s\n", account.ID)
	return 0
}
Example #14
0
func (m *Mailer) HandleDelivery(next func(conn *smtpd.Connection)) func(conn *smtpd.Connection) {
	return func(conn *smtpd.Connection) {
		// Context variables
		var (
			isSpam = false
			ctxID  = uniuri.NewLen(uniuri.UUIDLen)
		)

		// Check for spam
		spamReply, err := m.Spam.Report(string(conn.Envelope.Data))
		if err != nil {
			m.Log.WithFields(logrus.Fields{
				"ctx_id": ctxID,
				"err":    err,
			}).Error("Unable to check an email in spamd")
		}
		if spamReply != nil && spamReply.Code == spamc.EX_OK {
			if spam, ok := spamReply.Vars["isSpam"]; ok && spam.(bool) {
				isSpam = true
			}
		}

		// Trim the spaces from the input
		conn.Envelope.Data = bytes.TrimSpace(conn.Envelope.Data)

		// First run the analysis algorithm to generate an email description
		node := &models.EmailNode{}
		if err := analyzeEmail(node, conn.Envelope.Data); err != nil {
			m.Error(conn, err)
			return
		}

		// Calculate message ID
		messageID := node.Headers.Get("Message-ID")
		x1i := strings.Index(messageID, "<")
		if x1i != -1 {
			x2i := strings.Index(messageID[x1i+1:], ">")
			if x2i != -1 {
				messageID = messageID[x1i+1 : x1i+x2i+1]
			}
		}
		if messageID == "" {
			messageID = uniuri.NewLen(uniuri.UUIDLen) + "@invalid-incoming.pgp.st"
		}

		// Generate the members field
		fromHeader, err := mail.ParseAddress(node.Headers.Get("From"))
		if err != nil {
			m.Error(conn, err)
			return
		}
		members := []string{fromHeader.Address}

		toHeader, err := node.Headers.AddressList("From")
		if err != nil {
			m.Error(conn, err)
			return
		}
		for _, to := range toHeader {
			members = append(members, to.Address)
		}

		if x := node.Headers.Get("CC"); x != "" {
			ccHeader, err := node.Headers.AddressList("CC")
			if err != nil {
				m.Error(conn, err)
				return
			}
			for _, cc := range ccHeader {
				members = append(members, cc.Address)
			}
		}

		// Get the recipients list from the scope
		recipients := conn.Environment["recipients"].([]recipient)
		for _, recipient := range recipients {
			// Parse the found key
			keyring, err := openpgp.ReadKeyRing(bytes.NewReader(recipient.Key.Body))
			if err != nil {
				m.Error(conn, err)
				return
			}

			// Prepare a new email object
			email := &models.Email{
				ID:           uniuri.NewLen(uniuri.UUIDLen),
				DateCreated:  time.Now(),
				DateModified: time.Now(),
				Owner:        recipient.Account.ID,
				MessageID:    messageID,
				Status:       "received",
			}

			// Generate a new key
			key := make([]byte, 32)
			if _, err := io.ReadFull(rand.Reader, key); err != nil {
				m.Error(conn, err)
				return
			}
			akey := [32]byte{}
			copy(akey[:], key)

			// Generate a new nonce
			nonce := make([]byte, 8)
			if _, err = rand.Read(nonce); err != nil {
				m.Error(conn, err)
				return
			}

			// Set up a new stream
			stream, err := chacha20.New(key, nonce)
			if err != nil {
				m.Error(conn, err)
				return
			}

			// Prepare output
			ciphertext := make(
				[]byte,
				len(conn.Envelope.Data)+
					chacha20.NonceSize+
					(len(conn.Envelope.Data)/1024+1)*16,
			)

			// First write the nonce
			copy(ciphertext[:chacha20.NonceSize], nonce)

			// Then write the authenticated encrypted stream
			oi := chacha20.NonceSize
			for i := 0; i < len(conn.Envelope.Data); i += 1024 {
				// Get the chunk
				max := i + 1024
				if max > len(conn.Envelope.Data) {
					max = len(conn.Envelope.Data)
				}
				chunk := conn.Envelope.Data[i:max]

				// Encrypt the block
				stream.XORKeyStream(ciphertext[oi:oi+len(chunk)], chunk)

				// Authenticate it
				var out [16]byte
				poly1305.Sum(&out, ciphertext[oi:oi+len(chunk)], &akey)

				// Write it into the result
				copy(ciphertext[oi+len(chunk):oi+len(chunk)+poly1305.TagSize], out[:])

				// Increase the counter
				oi += 1024 + poly1305.TagSize
			}

			// Save it to the body
			email.Body = ciphertext

			// Create a manifest and encrypt it
			manifest, err := json.Marshal(models.Manifest{
				Key:         key,
				Nonce:       nonce,
				Description: node,
			})
			if err != nil {
				m.Error(conn, err)
				return
			}
			encryptedManifest, err := utils.PGPEncrypt(manifest, keyring)
			if err != nil {
				m.Error(conn, err)
				return
			}
			email.Manifest = encryptedManifest

			// Match the thread
			var thread *models.Thread

			// Get the References header
			var references string
			if x := node.Headers.Get("In-Reply-To"); x != "" {
				references = x
			} else if x := node.Headers.Get("References"); x != "" {
				references = x
			}

			// Match by Message-ID
			if references != "" {
				// As specified in http://www.jwz.org/doc/threading.html, first thing <> is the msg id
				// We support both <message-id> and message-id format.
				x1i := strings.Index(references, "<")
				if x1i != -1 {
					x2i := strings.Index(references[x1i+1:], ">")
					if x2i != -1 {
						references = references[x1i+1 : x1i+x2i+1]
					}
				}

				// Look up the message ID in the database
				cursor, err := r.Table("emails").GetAllByIndex("messageIDOwner", []interface{}{
					references,
					recipient.Account.ID,
				}).CoerceTo("array").Do(func(emails r.Term) r.Term {
					return r.Branch(
						emails.Count().Eq(1),
						r.Table("threads").Get(emails.Nth(0).Field("thread")).Default(map[string]interface{}{}),
						map[string]interface{}{},
					)
				}).Run(m.Rethink)
				if err != nil {
					m.Error(conn, err)
					return
				}
				defer cursor.Close()
				if err := cursor.One(&thread); err != nil {
					m.Error(conn, err)
					return
				}

				// Check if we've found it, clear it if it's invalid
				if thread.ID == "" {
					thread = nil
				}
			}

			// We can't match it by subject, so proceed to create a new thread
			if thread == nil {
				var secure string
				if findEncrypted(node) {
					secure = "all"
				} else {
					secure = "none"
				}

				labels := []string{recipient.Labels.Inbox}
				if isSpam {
					labels = append(labels, recipient.Labels.Spam)
				}

				thread = &models.Thread{
					ID:           uniuri.NewLen(uniuri.UUIDLen),
					DateCreated:  time.Now(),
					DateModified: time.Now(),
					Owner:        recipient.Account.ID,
					Labels:       labels,
					Members:      members,
					Secure:       secure,
				}

				if err := r.Table("threads").Insert(thread).Exec(m.Rethink); err != nil {
					m.Error(conn, err)
					return
				}
			} else {
				// Modify the existing thread
				foundInbox := false
				foundSpam := false
				for _, label := range thread.Labels {
					if label == recipient.Labels.Inbox {
						foundInbox = true
					}

					if isSpam {
						if label == recipient.Labels.Spam {
							foundSpam = true
						}

						if foundInbox && foundSpam {
							break
						}
					} else {
						if foundInbox {
							break
						}
					}
				}

				// Append to thread.Labels
				if !foundInbox {
					thread.Labels = append(thread.Labels, recipient.Labels.Inbox)
				}
				if !foundSpam && isSpam {
					thread.Labels = append(thread.Labels, recipient.Labels.Spam)
				}

				// Members update
				membersHash := map[string]struct{}{}
				for _, member := range thread.Members {
					membersHash[member] = struct{}{}
				}
				for _, member := range members {
					if _, ok := membersHash[member]; !ok {
						membersHash[member] = struct{}{}
					}
				}
				thread.Members = []string{}
				for member := range membersHash {
					thread.Members = append(thread.Members, member)
				}

				update := map[string]interface{}{
					"date_modified": time.Now(),
					"is_read":       false,
					"labels":        thread.Labels,
					"members":       thread.Members,
				}

				secure := findEncrypted(node)
				if (thread.Secure == "all" && !secure) ||
					(thread.Secure == "none" && secure) {
					thread.Secure = "some"
				}

				if err := r.Table("threads").Get(thread.ID).Update(update).Exec(m.Rethink); err != nil {
					m.Error(conn, err)
					return
				}
			}

			email.Thread = thread.ID
			if err := r.Table("emails").Insert(email).Exec(m.Rethink); err != nil {
				m.Error(conn, err)
				return
			}

			m.Log.WithFields(logrus.Fields{
				"address": recipient.Address.ID,
				"account": recipient.Account.MainAddress,
			}).Info("Email received")
		}

		next(conn)
	}
}
Example #15
0
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
}
Example #16
0
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
}
Example #17
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)
}
Example #18
0
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
}
Example #19
0
func databaseMigrate(c *cli.Context) int {
	// Connect to RethinkDB
	opts, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Get the migration status from the database
	version, err := getDatabaseVersion(opts, session)
	if err != nil {
		writeError(c, err)
		return 1
	}

	// Show the current migration's status
	fmt.Fprintf(c.App.Writer, "Current database schema's version is %d.\n", version)
	fmt.Fprintf(c.App.Writer, "Latest migration's version is %d.\n", len(migrations)-1)

	// Only proceed if the schema is outdated
	if version >= len(migrations)-1 {
		fmt.Fprintln(c.App.Writer, "Your schema is up to date.")
		return 0
	}

	// I don't know why would anyone use it, but it's here
	if c.Bool("no") {
		fmt.Fprintln(c.App.Writer, "Aborting the command because of the --no option.")
		return 1
	}

	// Ask for confirmations
	if !c.Bool("yes") {
		want, err := utils.AskForConfirmation(
			c.App.Writer,
			c.App.Env["reader"].(io.Reader),
			"Would you like to run "+strconv.Itoa(len(migrations)-1-version)+" migrations? [y/n]: ",
		)
		if err != nil {
			writeError(c, err)
			return 1
		}

		if !want {
			fmt.Fprintln(c.App.Writer, "Aborting the command.")
			return 1
		}
	}

	// Collect all queries
	queries := []r.Term{}
	for _, migration := range migrations[version+1:] {
		queries = append(queries, migration.Migrate(opts)...)
		queries = append(queries, r.Table("migration_status").Get("revision").Update(map[string]interface{}{
			"value": migration.Revision,
		}))
	}

	// Create a new progress bar
	bar := pb.StartNew(len(queries))
	for i, query := range queries {
		if c.Bool("dry") {
			fmt.Fprintf(c.App.Writer, "Executing %s\n", query.String())
		} else {
			if err := query.Exec(session); err != nil {
				bar.FinishPrint("Failed to execute migration #" + strconv.Itoa(i) + ":")
				fmt.Fprintf(c.App.Writer, "\tQuery: %s\n", query.String())
				fmt.Fprintf(c.App.Writer, "\tError: %v\n", err)
				return 1
			}
		}
		bar.Increment()
	}

	// Show a "finished" message
	bar.FinishPrint("Migration completed. " + strconv.Itoa(len(queries)) + " queries executed.")
	return 0
}
Example #20
0
func accountsList(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Get accounts without passwords from database
	cursor, err := r.Table("accounts").Map(func(row r.Term) r.Term {
		return row.Without("password").Merge(map[string]interface{}{
			"addresses": r.Table("addresses").GetAllByIndex("owner", row.Field("id")).CoerceTo("array"),
		})
	}).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	var accounts []struct {
		models.Account
		Addresses []*models.Address `gorethink:"addresses" json:"addresses`
	}
	if err := cursor.All(&accounts); err != nil {
		writeError(c, err)
		return 1
	}

	// Write the output
	if c.Bool("json") {
		if err := json.NewEncoder(c.App.Writer).Encode(accounts); err != nil {
			writeError(c, err)
			return 1
		}

		fmt.Fprint(c.App.Writer, "\n")
	} else {
		table := termtables.CreateTable()
		table.AddHeaders("id", "addresses", "subscription", "status", "date_created")
		for _, account := range accounts {
			emails := []string{}

			for _, address := range account.Addresses {
				if address.ID == account.MainAddress {
					address.StyledID = "*" + address.StyledID
					emails = append([]string{address.StyledID}, emails...)
				} else {
					emails = append(emails, address.StyledID)
				}
			}

			table.AddRow(
				account.ID,
				strings.Join(emails, ", "),
				account.Subscription,
				account.Status,
				account.DateCreated.Format(time.RubyDate),
			)
		}
		fmt.Fprintln(c.App.Writer, table.Render())
	}

	return 0
}
Example #21
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
}
Example #22
0
func (m *Mailer) HandleRecipient(next func(conn *smtpd.Connection)) func(conn *smtpd.Connection) {
	return func(conn *smtpd.Connection) {
		// Prepare the context
		if conn.Environment == nil {
			conn.Environment = map[string]interface{}{}
		}
		if _, ok := conn.Environment["recipients"]; !ok {
			conn.Environment["recipients"] = []recipient{}
		}
		recipients := conn.Environment["recipients"].([]recipient)

		// Get the most recently added recipient and parse it
		addr, err := mail.ParseAddress(
			conn.Envelope.Recipients[len(conn.Envelope.Recipients)-1],
		)
		if err != nil {
			m.Error(conn, err)
			return
		}

		// Normalize the address
		addr.Address = utils.RemoveDots(utils.NormalizeAddress(addr.Address))

		// Fetch the address and account from database
		cursor, err := r.Table("addresses").Get(addr.Address).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,
				),
				"key": r.Branch(
					address.HasFields("public_key").And(address.Field("public_key").Ne("")),
					r.Table("keys").Get(address.Field("public_key")).Without("identities"),
					r.Branch(
						address.HasFields("id"),
						r.Table("keys").GetAllByIndex("owner", address.Field("owner")).OrderBy("date_created").CoerceTo("array").Do(func(keys r.Term) r.Term {
							return r.Branch(
								keys.Count().Gt(0),
								keys.Nth(-1).Without("identities"),
								nil,
							)
						}),
						nil,
					),
				),
			}
		}).Do(func(data r.Term) r.Term {
			return data.Merge(map[string]interface{}{
				"labels": r.Branch(
					data.Field("account").HasFields("id"),
					r.Table("labels").GetAllByIndex("nameOwnerSystem", []interface{}{
						"Inbox",
						data.Field("account").Field("id"),
						true,
					}, []interface{}{
						"Spam",
						data.Field("account").Field("id"),
						true,
					}).CoerceTo("array").Do(func(result r.Term) r.Term {
						return r.Branch(
							result.Count().Eq(2),
							map[string]interface{}{
								"inbox": result.Nth(0).Field("id"),
								"spam":  result.Nth(1).Field("id"),
							},
							nil,
						)
					}),
					nil,
				),
			})
		}).Run(m.Rethink)
		if err != nil {
			m.Error(conn, err)
			return
		}
		defer cursor.Close()
		var result recipient
		if err := cursor.One(&result); err != nil {
			m.Error(conn, err)
			return
		}

		// Check if anything got matched
		if result.Address == nil || result.Address.ID == "" || result.Account == nil || result.Account.ID == "" {
			conn.Error(errors.New("No such address"))
			return
		}
		if result.Key == nil || result.Labels.Inbox == "" || result.Labels.Spam == "" {
			conn.Error(errors.New("Account is not configured"))
			return
		}

		// Append the result to the recipients
		conn.Environment["recipients"] = append(recipients, result)

		// Run the next handler
		next(conn)
	}
}
Example #23
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
}
Example #24
0
func addressesAdd(c *cli.Context) int {
	// Connect to RethinkDB
	_, session, connected := connectToRethinkDB(c)
	if !connected {
		return 1
	}

	// Input struct
	var input struct {
		ID    string `json:"id"`
		Owner string `json:"owner"`
	}

	// Read JSON from stdin
	if c.Bool("json") {
		if err := json.NewDecoder(c.App.Env["reader"].(io.Reader)).Decode(&input); err != nil {
			writeError(c, err)
			return 1
		}
	} else {
		// Buffer stdin
		rd := bufio.NewReader(c.App.Env["reader"].(io.Reader))
		var err error

		// Acquire from interactive input
		fmt.Fprintf(c.App.Writer, "Address: ")
		input.ID, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.ID = strings.TrimSpace(input.ID)

		fmt.Fprintf(c.App.Writer, "Owner ID: ")
		input.Owner, err = rd.ReadString('\n')
		if err != nil {
			writeError(c, err)
			return 1
		}
		input.Owner = strings.TrimSpace(input.Owner)
	}

	// First of all, the address. Append domain if it has no such suffix.
	if strings.Index(input.ID, "@") == -1 {
		input.ID += "@" + c.GlobalString("default_domain")
	}

	// And format it
	styledID := utils.NormalizeAddress(input.ID)
	input.ID = utils.RemoveDots(styledID)

	// Then check if it's taken.
	cursor, err := r.Table("addresses").Get(input.ID).Ne(nil).Run(session)
	if err != nil {
		writeError(c, err)
		return 1
	}
	defer cursor.Close()
	var taken bool
	if err := cursor.One(&taken); err != nil {
		writeError(c, err)
		return 1
	}
	if taken {
		writeError(c, fmt.Errorf("Address %s is already taken", input.ID))
		return 1
	}

	// Check if account ID exists
	cursor, err = r.Table("accounts").Get(input.Owner).Ne(nil).Run(session)
	if err != nil {
		writeError(c, err)
	}
	defer cursor.Close()
	var exists bool
	if err := cursor.One(&exists); err != nil {
		writeError(c, err)
		return 1
	}
	if !exists {
		writeError(c, fmt.Errorf("Account %s doesn't exist", input.ID))
		return 1
	}

	// Insert the address into the database
	address := &models.Address{
		ID:           input.ID,
		StyledID:     styledID,
		DateCreated:  time.Now(),
		DateModified: time.Now(),
		Owner:        input.Owner,
	}

	if !c.GlobalBool("dry") {
		if err := r.Table("addresses").Insert(address).Exec(session); err != nil {
			writeError(c, err)
			return 1
		}
	}

	// Write a success message
	fmt.Fprintf(c.App.Writer, "Created a new address - %s\n", address.StyledID)
	return 0
}
Example #25
0
type migration struct {
	Revision int
	Name     string
	Migrate  func(*r.ConnectOpts) []r.Term
	Revert   func(*r.ConnectOpts) []r.Term
}

var migrations = []migration{
	{
		Revision: 0,
		Name:     "create_tables",
		Migrate: func(opts *r.ConnectOpts) []r.Term {
			return []r.Term{
				r.DB(opts.Database).TableCreate("accounts"),
				r.Table("accounts").IndexCreate("alt_email"),
				r.Table("accounts").IndexCreate("main_address"),
				r.DB(opts.Database).TableCreate("addresses"),
				r.DB(opts.Database).TableCreate("applications"),
				r.DB(opts.Database).TableCreate("emails"),
				r.DB(opts.Database).TableCreate("keys"),
				r.DB(opts.Database).TableCreate("labels"),
				r.DB(opts.Database).TableCreate("resources"),
				r.DB(opts.Database).TableCreate("threads"),
				r.DB(opts.Database).TableCreate("tokens"),
			}
		},
		Revert: func(opts *r.ConnectOpts) []r.Term {
			return []r.Term{
				r.DB(opts.Database).TableDrop("accounts"),
				r.DB(opts.Database).TableDrop("addresses"),