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