Example #1
0
func getCluster(ctx scope.Context) (cluster.Cluster, error) {
	era, err := snowflake.New()
	if err != nil {
		return nil, fmt.Errorf("era error: %s", err)
	}

	backend.Config.Cluster.Era = era.String()
	backend.Config.Cluster.Version = Version

	c, err := backend.Config.Cluster.EtcdCluster(ctx)
	if err != nil {
		return nil, fmt.Errorf("cluster error: %s", err)
	}

	if *config == "" {
		if err := backend.Config.LoadFromEtcd(c); err != nil {
			return nil, fmt.Errorf("config: %s", err)
		}
	} else {
		if err := backend.Config.LoadFromFile(*config); err != nil {
			return nil, fmt.Errorf("config: %s", err)
		}
	}

	return c, nil
}
Example #2
0
File: jobs.go Project: logan/heim
func (jq *JobQueueBinding) newJob(
	jobType jobs.JobType, payload interface{}, options ...jobs.JobOption) (*jobs.Job, error) {

	jobID, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	now := time.Now()
	job := &jobs.Job{
		ID:                jobID,
		Type:              jobType,
		Created:           now,
		Due:               now,
		AttemptsRemaining: math.MaxInt32,
		MaxWorkDuration:   jobs.DefaultMaxWorkDuration,
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}
	if err := job.Data.UnmarshalJSON(data); err != nil {
		return nil, err
	}

	for _, option := range options {
		if err := option.Apply(job); err != nil {
			return nil, err
		}
	}

	return job, nil
}
Example #3
0
func NewController(ctx scope.Context, heim *proto.Heim, workerName, queueName string) (*Controller, error) {
	jq, err := heim.Backend.Jobs().GetQueue(ctx, queueName)
	if err != nil {
		return nil, err
	}

	worker, ok := workers[queueName]
	if !ok {
		return nil, fmt.Errorf("no worker registered for queue %s", queueName)
	}

	sf, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	if err := worker.Init(heim); err != nil {
		return nil, err
	}

	ctrl := &Controller{
		id: fmt.Sprintf("%s-%s", workerName, sf),
		jq: jq,
		w:  worker,
	}
	return ctrl, nil
}
Example #4
0
File: room.go Project: logan/heim
func (r *RoomBase) EditMessage(
	ctx scope.Context, session proto.Session, edit proto.EditMessageCommand) (
	proto.EditMessageReply, error) {

	r.m.Lock()
	defer r.m.Unlock()

	editID, err := snowflake.New()
	if err != nil {
		return proto.EditMessageReply{}, err
	}

	msg, err := r.log.edit(edit)
	if err != nil {
		return proto.EditMessageReply{}, err
	}

	if edit.Announce {
		event := &proto.EditMessageEvent{
			EditID:  editID,
			Message: *msg,
		}
		if err := r.broadcast(ctx, proto.EditMessageType, event, session); err != nil {
			return proto.EditMessageReply{}, err
		}
	}

	reply := proto.EditMessageReply{
		EditID:  editID,
		Message: *msg,
	}
	return reply, nil
}
Example #5
0
File: smtp.go Project: rmasoni/heim
func (s *SMTPDeliverer) MessageID() (string, error) {
	sf, err := snowflake.New()
	if err != nil {
		return "", fmt.Errorf("%s: snowflake error: %s", s, err)
	}
	return fmt.Sprintf("<%s@%s>", sf, s.localName), nil
}
Example #6
0
File: pm.go Project: logan/heim
func NewPM(kms security.KMS, client *Client, initiatorNick string, receiver UserID, receiverNick string) (
	*PM, *security.ManagedKey, error) {

	if client.Account == nil {
		return nil, nil, ErrAccessDenied
	}

	pmID, err := snowflake.New()
	if err != nil {
		return nil, nil, err
	}

	iv, err := kms.GenerateNonce(RoomMessageKeyType.BlockSize())
	if err != nil {
		return nil, nil, err
	}

	encryptedSystemKey, err := kms.GenerateEncryptedKey(RoomMessageKeyType, "pm", pmID.String())
	if err != nil {
		return nil, nil, err
	}

	pmKey := encryptedSystemKey.Clone()
	if err := kms.DecryptKey(&pmKey); err != nil {
		return nil, nil, fmt.Errorf("pm key decrypt: %s", err)
	}
	//pmKey.IV = iv

	userKey := client.Account.UserKey()
	if err := userKey.Decrypt(client.Authorization.ClientKey); err != nil {
		return nil, nil, fmt.Errorf("initiator account key decrypt: %s", err)
	}

	encryptedInitiatorKey := pmKey.Clone()
	encryptedInitiatorKey.IV = iv
	if err := encryptedInitiatorKey.Encrypt(&userKey); err != nil {
		return nil, nil, fmt.Errorf("initiator pm key encrypt: %s", err)
	}

	var (
		mac [16]byte
		key [32]byte
	)
	copy(key[:], pmKey.Plaintext)
	poly1305.Sum(&mac, []byte(receiver), &key)

	pm := &PM{
		ID:                    pmID,
		Initiator:             client.Account.ID(),
		InitiatorNick:         initiatorNick,
		Receiver:              receiver,
		ReceiverNick:          receiverNick,
		ReceiverMAC:           mac[:],
		IV:                    iv,
		EncryptedSystemKey:    encryptedSystemKey,
		EncryptedInitiatorKey: &encryptedInitiatorKey,
	}
	return pm, &pmKey, nil
}
Example #7
0
File: emails.go Project: logan/heim
func (et *EmailTracker) Send(
	ctx scope.Context, js jobs.JobService, templater *templates.Templater, deliverer emails.Deliverer,
	account proto.Account, to, templateName string, data interface{}) (
	*emails.EmailRef, error) {

	if to == "" {
		to, _ = account.Email()
	}

	sf, err := snowflake.New()
	if err != nil {
		return nil, err
	}
	msgID := fmt.Sprintf("<%s@%s>", sf, deliverer.LocalName())

	ref, err := emails.NewEmail(templater, msgID, to, templateName, data)
	if err != nil {
		return nil, err
	}
	ref.AccountID = account.ID()

	jq, err := js.GetQueue(ctx, jobs.EmailQueue)
	if err != nil {
		return nil, err
	}

	payload := &jobs.EmailJob{
		AccountID: account.ID(),
		EmailID:   ref.ID,
	}
	job, err := jq.AddAndClaim(ctx, jobs.EmailJobType, payload, "immediate", jobs.EmailJobOptions...)
	if err != nil {
		return nil, err
	}

	ref.JobID = job.ID

	et.m.Lock()
	if et.emailsByAccount == nil {
		et.emailsByAccount = map[snowflake.Snowflake][]*emails.EmailRef{}
	}
	et.emailsByAccount[account.ID()] = append(et.emailsByAccount[account.ID()], ref)
	et.m.Unlock()

	child := ctx.Fork()
	child.WaitGroup().Add(1)

	go job.Exec(child, func(ctx scope.Context) error {
		defer ctx.WaitGroup().Done()

		logging.Logger(ctx).Printf("delivering to %s\n", to)
		if err := deliverer.Deliver(ctx, ref); err != nil {
			return err
		}
		return nil
	})

	return ref, nil
}
Example #8
0
func (s *session) handleSendCommand(cmd *proto.SendCommand) *response {
	if s.Identity().Name() == "" {
		return &response{err: fmt.Errorf("you must choose a name before you may begin chatting")}
	}

	if len(cmd.Content) > proto.MaxMessageLength {
		return &response{err: proto.ErrMessageTooLong}
	}

	msgID, err := snowflake.New()
	if err != nil {
		return &response{err: err}
	}

	var isValidParent bool
	if s.managedRoom != nil {
		isValidParent, err = s.managedRoom.IsValidParent(cmd.Parent)
	} else {
		isValidParent, err = s.room.IsValidParent(cmd.Parent)
	}
	if err != nil {
		return &response{err: err}
	}
	if !isValidParent {
		return &response{err: proto.ErrInvalidParent}
	}
	msg := proto.Message{
		ID:      msgID,
		Content: cmd.Content,
		Parent:  cmd.Parent,
		Sender:  s.View(proto.Host),
	}

	if s.keyID != "" {
		key := s.client.Authorization.MessageKeys[s.keyID]
		if err := proto.EncryptMessage(&msg, s.keyID, key); err != nil {
			return &response{err: err}
		}
	}

	sent, err := s.room.Send(s.ctx, s, msg)
	if err != nil {
		return &response{err: err}
	}

	if s.privilegeLevel() == proto.General {
		sent.Sender.ClientAddress = ""
	}

	packet, err := proto.DecryptPayload(proto.SendReply(sent), &s.client.Authorization, s.privilegeLevel())
	return &response{
		packet: packet,
		err:    err,
		cost:   10,
	}
}
Example #9
0
File: config.go Project: logan/heim
func getHeim(ctx scope.Context) (*proto.Heim, error) {
	cfg, err := getConfig(ctx)
	if err != nil {
		return nil, err
	}

	era, err := snowflake.New()
	if err != nil {
		return nil, fmt.Errorf("era error: %s", err)
	}

	cfg.Cluster.Era = era.String()
	cfg.Cluster.Version = Version
	return cfg.Heim(ctx)
}
Example #10
0
func NewAccount(kms security.KMS, password string) (proto.Account, *security.ManagedKey, error) {
	id, err := snowflake.New()
	if err != nil {
		return nil, nil, err
	}

	sec, clientKey, err := proto.NewAccountSecurity(kms, password)
	if err != nil {
		return nil, nil, err
	}

	account := &memAccount{
		id:  id,
		sec: *sec,
	}
	return account, clientKey, nil
}
Example #11
0
func (s *session) handleSendCommand(cmd *proto.SendCommand) *response {
	if s.Identity().Name() == "" {
		return &response{err: fmt.Errorf("you must choose a name before you may begin chatting")}
	}

	msgID, err := snowflake.New()
	if err != nil {
		return &response{err: err}
	}

	isValidParent, err := s.room.IsValidParent(cmd.Parent)
	if err != nil {
		return &response{err: err}
	}
	if !isValidParent {
		return &response{err: proto.ErrInvalidParent}
	}
	msg := proto.Message{
		ID:      msgID,
		Content: cmd.Content,
		Parent:  cmd.Parent,
		Sender:  s.View(),
	}

	if s.keyID != "" {
		key := s.client.Authorization.MessageKeys[s.keyID]
		if err := proto.EncryptMessage(&msg, s.keyID, key); err != nil {
			return &response{err: err}
		}
	}

	sent, err := s.room.Send(s.ctx, s, msg)
	if err != nil {
		return &response{err: err}
	}

	packet, err := proto.DecryptPayload(proto.SendReply(sent), &s.client.Authorization)
	return &response{
		packet: packet,
		err:    err,
		cost:   10,
	}
}
Example #12
0
File: room.go Project: logan/heim
func (r *Room) generateMessageKey(b *Backend, kms security.KMS) (*RoomMessageKeyBinding, error) {
	// Generate unique ID for storing new key in DB.
	keyID, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	// Use KMS to generate nonce and key.
	nonce, err := kms.GenerateNonce(proto.RoomManagerKeyType.KeySize())
	if err != nil {
		return nil, err
	}

	mkey, err := kms.GenerateEncryptedKey(proto.RoomManagerKeyType, "room", r.Name)
	if err != nil {
		return nil, err
	}

	return NewRoomMessageKeyBinding(r.Bind(b), keyID, mkey, nonce), nil
}
Example #13
0
func GeneratePasswordResetRequest(
	kms security.KMS, accountID snowflake.Snowflake) (*PasswordResetRequest, error) {

	id, err := snowflake.New()
	if err != nil {
		return nil, err
	}

	key, err := kms.GenerateNonce(sha256.BlockSize)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	req := &PasswordResetRequest{
		ID:        id,
		AccountID: accountID,
		Key:       key,
		Requested: now,
		Expires:   now.Add(PasswordResetRequestLifetime),
	}
	return req, nil
}
Example #14
0
func (et *EmailTracker) Send(
	ctx scope.Context, js jobs.JobService, templater *templates.Templater, deliverer emails.Deliverer,
	account proto.Account, templateName string, data interface{}) (
	*emails.EmailRef, error) {

	sf, err := snowflake.New()
	if err != nil {
		return nil, err
	}
	msgID := fmt.Sprintf("<%s@%s>", sf, deliverer.LocalName())

	to := ""
	/*
		requireVerifiedAddress := true
		switch templateName {
		case proto.WelcomeEmail, proto.RoomInvitationWelcomeEmail, proto.PasswordResetEmail:
			requireVerifiedAddress = false
		}
	*/
	for _, pid := range account.PersonalIdentities() {
		if pid.Namespace() == "email" {
			/*
				if !pid.Verified() && requireVerifiedAddress {
					continue
				}
			*/
			to = pid.ID()
			break
		}
	}
	if to == "" {
		fmt.Printf("no email address to deliver to\n")
		return nil, fmt.Errorf("account has no email address to deliver %s to", templateName)
	}

	ref, err := emails.NewEmail(templater, msgID, to, templateName, data)
	if err != nil {
		return nil, err
	}
	ref.AccountID = account.ID()

	jq, err := js.GetQueue(ctx, jobs.EmailQueue)
	if err != nil {
		return nil, err
	}

	payload := &jobs.EmailJob{
		AccountID: account.ID(),
		EmailID:   ref.ID,
	}
	job, err := jq.AddAndClaim(ctx, jobs.EmailJobType, payload, "immediate", jobs.EmailJobOptions...)
	if err != nil {
		return nil, err
	}

	ref.JobID = job.ID

	et.m.Lock()
	if et.emailsByAccount == nil {
		et.emailsByAccount = map[snowflake.Snowflake][]*emails.EmailRef{}
	}
	et.emailsByAccount[account.ID()] = append(et.emailsByAccount[account.ID()], ref)
	et.m.Unlock()

	child := ctx.Fork()
	child.WaitGroup().Add(1)

	go job.Exec(child, func(ctx scope.Context) error {
		defer ctx.WaitGroup().Done()

		logging.Logger(ctx).Printf("delivering to %s\n", to)
		if err := deliverer.Deliver(ctx, ref); err != nil {
			return err
		}
		return nil
	})

	return ref, nil
}
Example #15
0
func (et *EmailTracker) Send(
	ctx scope.Context, js jobs.JobService, templater *templates.Templater, deliverer emails.Deliverer,
	account proto.Account, templateName string, data interface{}) (
	*emails.EmailRef, error) {

	// choose a Message-ID
	sf, err := snowflake.New()
	if err != nil {
		return nil, err
	}
	domain := "heim"
	if deliverer != nil {
		domain = deliverer.LocalName()
	}
	msgID := fmt.Sprintf("<%s@%s>", sf, domain)

	// choose an address to send to
	to := ""
	/*
	   requireVerifiedAddress := true
	   switch templateName {
	   case proto.WelcomeEmail, proto.RoomInvitationWelcomeEmail, proto.PasswordResetEmail:
	       requireVerifiedAddress = false
	   }
	*/
	for _, pid := range account.PersonalIdentities() {
		if pid.Namespace() == "email" {
			/*
			   if !pid.Verified() && requireVerifiedAddress {
			       continue
			   }
			*/
			to = pid.ID()
			break
		}
	}
	if to == "" {
		fmt.Printf("no email address to deliver to\n")
		return nil, fmt.Errorf("account has no email address to deliver %s to", templateName)
	}

	// construct the email
	ref, err := emails.NewEmail(templater, msgID, to, templateName, data)
	if err != nil {
		return nil, err
	}
	ref.AccountID = account.ID()

	// get underlying JobQueue so we can add-and-claim in the same transaction as the email insert
	abstractQueue, err := js.GetQueue(ctx, jobs.EmailQueue)
	if err != nil {
		return nil, err
	}
	jq := abstractQueue.(*JobQueueBinding)

	t, err := et.Backend.DbMap.Begin()
	if err != nil {
		return nil, err
	}

	// insert job first, so we know what JobID to associate with the email when we insert it
	payload := &jobs.EmailJob{
		AccountID: account.ID(),
		EmailID:   ref.ID,
	}
	job, err := jq.addAndClaim(ctx, t, jobs.EmailJobType, payload, "immediate", jobs.EmailJobOptions...)
	if err != nil {
		rollback(ctx, t)
		return nil, err
	}
	ref.JobID = job.ID

	// insert the email
	var email Email
	email.FromBackend(ref)
	if err := t.Insert(&email); err != nil {
		rollback(ctx, t)
		return nil, err
	}

	// finalize and spin off first delivery attempt
	if err := t.Commit(); err != nil {
		return nil, err
	}

	child := ctx.Fork()
	child.WaitGroup().Add(1)
	go job.Exec(child, func(ctx scope.Context) error {
		defer ctx.WaitGroup().Done()

		logging.Logger(ctx).Printf("delivering to %s\n", to)
		if deliverer == nil {
			return fmt.Errorf("deliverer not configured")
		}
		if err := deliverer.Deliver(ctx, ref); err != nil {
			return err
		}
		if _, err := et.Backend.DbMap.Exec("UPDATE email SET delivered = $2 WHERE id = $1", ref.ID, ref.Delivered); err != nil {
			// Even if we fail to mark the email as delivered, don't return an
			// error so the job still gets completed. We wouldn't want to spam
			// someone just because of a DB issue.
			logging.Logger(ctx).Printf("error marking email %s/%s as delivered: %s", account.ID(), ref.ID, err)
		}
		return nil
	})

	return ref, nil
}
Example #16
0
File: room.go Project: logan/heim
func (rb *RoomBinding) EditMessage(
	ctx scope.Context, session proto.Session, edit proto.EditMessageCommand) (
	proto.EditMessageReply, error) {

	var reply proto.EditMessageReply

	editID, err := snowflake.New()
	if err != nil {
		return reply, err
	}

	cols, err := allColumns(rb.DbMap, Message{}, "")
	if err != nil {
		return reply, err
	}

	t, err := rb.DbMap.Begin()
	if err != nil {
		return reply, err
	}

	rollback := func() {
		if err := t.Rollback(); err != nil {
			logging.Logger(ctx).Printf("rollback error: %s", err)
		}
	}

	var msg Message
	err = t.SelectOne(&msg, fmt.Sprintf("SELECT %s FROM message WHERE room = $1 AND id = $2", cols), rb.RoomName, edit.ID.String())
	if err != nil {
		rollback()
		return reply, err
	}

	if msg.PreviousEditID.Valid && msg.PreviousEditID.String != edit.PreviousEditID.String() {
		rollback()
		return reply, proto.ErrEditInconsistent
	}

	entry := &MessageEditLog{
		EditID:          editID.String(),
		Room:            rb.RoomName,
		MessageID:       edit.ID.String(),
		PreviousEditID:  msg.PreviousEditID,
		PreviousContent: msg.Content,
		PreviousParent: sql.NullString{
			String: msg.Parent,
			Valid:  true,
		},
	}
	// TODO: tests pass in a nil session, until we add support for the edit command
	if session != nil {
		entry.EditorID = sql.NullString{
			String: string(session.Identity().ID()),
			Valid:  true,
		}
	}
	if err := t.Insert(entry); err != nil {
		rollback()
		return reply, err
	}

	now := time.Time(proto.Now())
	sets := []string{"edited = $3", "previous_edit_id = $4"}
	args := []interface{}{rb.RoomName, edit.ID.String(), now, editID.String()}
	msg.Edited = gorp.NullTime{Valid: true, Time: now}
	if edit.Content != "" {
		args = append(args, edit.Content)
		sets = append(sets, fmt.Sprintf("content = $%d", len(args)))
		msg.Content = edit.Content
	}
	if edit.Parent != 0 {
		args = append(args, edit.Parent.String())
		sets = append(sets, fmt.Sprintf("parent = $%d", len(args)))
		msg.Parent = edit.Parent.String()
	}
	if edit.Delete != msg.Deleted.Valid {
		if edit.Delete {
			args = append(args, now)
			sets = append(sets, fmt.Sprintf("deleted = $%d", len(args)))
			msg.Deleted = gorp.NullTime{Valid: true, Time: now}
		} else {
			sets = append(sets, "deleted = NULL")
			msg.Deleted.Valid = false
		}
	}
	query := fmt.Sprintf("UPDATE message SET %s WHERE room = $1 AND id = $2", strings.Join(sets, ", "))
	if _, err := t.Exec(query, args...); err != nil {
		rollback()
		return reply, err
	}

	if edit.Announce {
		event := &proto.EditMessageEvent{
			EditID:  editID,
			Message: msg.ToTransmission(),
		}
		err = rb.broadcast(ctx, t, proto.EditMessageEventType, event, session)
		if err != nil {
			rollback()
			return reply, err
		}
	}

	if err := t.Commit(); err != nil {
		return reply, err
	}

	reply.EditID = editID
	reply.Message = msg.ToTransmission()
	return reply, nil
}
Example #17
0
func (b *AccountManagerBinding) Register(
	ctx scope.Context, kms security.KMS, namespace, id, password string,
	agentID string, agentKey *security.ManagedKey) (
	proto.Account, *security.ManagedKey, error) {

	// Generate ID for new account.
	accountID, err := snowflake.New()
	if err != nil {
		return nil, nil, err
	}

	// Generate credentials in advance of working in DB transaction.
	sec, clientKey, err := proto.NewAccountSecurity(kms, password)
	if err != nil {
		return nil, nil, err
	}

	// Begin transaction to check on identity availability and store new account data.
	t, err := b.DbMap.Begin()
	if err != nil {
		return nil, nil, err
	}

	rollback := func() {
		if err := t.Rollback(); err != nil {
			backend.Logger(ctx).Printf("rollback error: %s", err)
		}
	}

	// Insert new rows for account.
	account := &Account{
		ID:                  accountID.String(),
		Nonce:               sec.Nonce,
		MAC:                 sec.MAC,
		EncryptedSystemKey:  sec.SystemKey.Ciphertext,
		EncryptedUserKey:    sec.UserKey.Ciphertext,
		EncryptedPrivateKey: sec.KeyPair.EncryptedPrivateKey,
		PublicKey:           sec.KeyPair.PublicKey,
	}
	personalIdentity := &PersonalIdentity{
		Namespace: namespace,
		ID:        id,
		AccountID: accountID.String(),
	}
	if err := t.Insert(account); err != nil {
		rollback()
		return nil, nil, err
	}
	if err := t.Insert(personalIdentity); err != nil {
		rollback()
		if strings.HasPrefix(err.Error(), "pq: duplicate key value") {
			return nil, nil, proto.ErrPersonalIdentityInUse
		}
		return nil, nil, err
	}

	// Look up the associated agent.
	atb := &AgentTrackerBinding{b.Backend}
	agent, err := atb.getFromDB(agentID, t)
	if err != nil {
		rollback()
		return nil, nil, err
	}
	if err := agent.SetClientKey(agentKey, clientKey); err != nil {
		rollback()
		return nil, nil, err
	}
	err = atb.setClientKeyInDB(agentID, accountID.String(), agent.EncryptedClientKey.Ciphertext, t)
	if err != nil {
		rollback()
		return nil, nil, err
	}

	// Commit the transaction.
	if err := t.Commit(); err != nil {
		return nil, nil, err
	}
	backend.Logger(ctx).Printf("registered new account %s for %s:%s", account.ID, namespace, id)

	ab := account.Bind(b.Backend)
	ab.identities = []proto.PersonalIdentity{&PersonalIdentityBinding{personalIdentity}}
	return ab, clientKey, nil
}