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 (rb *RoomBinding) ManagerCapability(ctx scope.Context, manager proto.Account) ( security.Capability, error) { var c Capability err := rb.SelectOne( &c, "SELECT c.id, c.nonce, c.encrypted_private_data, c.public_data"+ " FROM capability c, room_manager_capability rm"+ " WHERE rm.room = $1 AND c.id = rm.capability_id AND c.account_id = $2"+ " AND rm.revoked < rm.granted", rb.Name, manager.ID().String()) if err != nil { if err == sql.ErrNoRows { return nil, proto.ErrManagerNotFound } } return &c, nil }
func (rmc *RoomManagerCapabilities) Save( ctx scope.Context, account proto.Account, c security.Capability) error { capRow := &Capability{ ID: c.CapabilityID(), NonceBytes: c.Nonce(), EncryptedPrivateData: c.EncryptedPayload(), PublicData: c.PublicPayload(), } rmCapRow := &RoomManagerCapability{ Room: rmc.Room.Name, CapabilityID: c.CapabilityID(), Granted: time.Now(), } if account != nil { capRow.AccountID = account.ID().String() rmCapRow.AccountID = account.ID().String() } return rmc.Executor.Insert(capRow, rmCapRow) }
func (r *memRoom) verifyManager(ctx scope.Context, actor proto.Account, actorKey *security.ManagedKey) ( *security.PublicKeyCapability, error) { // Verify that actorKey unlocks actor's keypair. In a real implementation, // we would take an additional step of verifying against a capability. kp := actor.KeyPair() if err := kp.Decrypt(actorKey); err != nil { return nil, err } // Verify actor is a manager. c, err := r.ManagerCapability(ctx, actor) if err != nil { if err == proto.ErrManagerNotFound { return nil, proto.ErrAccessDenied } return nil, err } return c.(*security.PublicKeyCapability), nil }
func (m *accountManager) GenerateOTP(ctx scope.Context, heim *proto.Heim, kms security.KMS, account proto.Account) (*proto.OTP, error) { m.b.Lock() defer m.b.Unlock() if m.b.otps == nil { m.b.otps = map[snowflake.Snowflake]*proto.OTP{} } old, ok := m.b.otps[account.ID()] if ok && old.Validated { return nil, proto.ErrOTPAlreadyEnrolled } otp, err := heim.NewOTP(account) if err != nil { return nil, err } m.b.otps[account.ID()] = otp return otp, nil }
func (rb *ManagedRoomBinding) ManagerCapability(ctx scope.Context, manager proto.Account) ( security.Capability, error) { var c Capability cols, err := allColumns(rb.DbMap, c, "c") if err != nil { return nil, err } err = rb.SelectOne( &c, fmt.Sprintf("SELECT %s FROM capability c, room_manager_capability rm"+ " WHERE rm.room = $1 AND c.id = rm.capability_id AND c.account_id = $2"+ " AND rm.revoked < rm.granted", cols), rb.Name, manager.ID().String()) if err != nil { if err == sql.ErrNoRows { return nil, proto.ErrManagerNotFound } } return &c, 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 (b *AccountManagerBinding) GenerateOTP(ctx scope.Context, heim *proto.Heim, kms security.KMS, account proto.Account) (*proto.OTP, error) { encryptedKey, err := kms.GenerateEncryptedKey(OTPKeyType, "account", account.ID().String()) if err != nil { return nil, err } key := encryptedKey.Clone() if err := kms.DecryptKey(&key); err != nil { return nil, err } iv, err := kms.GenerateNonce(OTPKeyType.BlockSize()) if err != nil { return nil, err } t, err := b.DbMap.Begin() if err != nil { return nil, err } rawOTP, err := b.getRawOTP(t, account.ID()) if err != nil && err != proto.ErrOTPNotEnrolled { rollback(ctx, t) return nil, err } if err == nil { if rawOTP.Validated { rollback(ctx, t) return nil, proto.ErrOTPAlreadyEnrolled } row := &OTP{AccountID: account.ID().String()} if _, err := t.Delete(row); err != nil { rollback(ctx, t) return nil, err } } otp, err := heim.NewOTP(account) if err != nil { rollback(ctx, t) return nil, err } digest, encryptedURI, err := security.EncryptGCM(&key, iv, []byte(otp.URI), nil) if err != nil { rollback(ctx, t) return nil, err } row := &OTP{ AccountID: account.ID().String(), IV: iv, EncryptedKey: encryptedKey.Ciphertext, Digest: digest, EncryptedURI: encryptedURI, } if err := t.Insert(row); err != nil { // TODO: this could fail in the case of a race condition // by the time that matters we should be on postgres 9.5 and using a proper upsert rollback(ctx, t) return nil, err } if err := t.Commit(); err != nil { return nil, err } return otp, 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 }