Skip to content
Snippets Groups Projects
Commit d18d9344 authored by Cam Saül's avatar Cam Saül
Browse files

Wire up new user notification email :e-mail:

parent 18b23dc0
No related merge requests found
......@@ -37,6 +37,7 @@
(pre-update 1)
(project 1)
(qp-expect-with-engines 1)
(render-file 1)
(resolve-private-fns 1)
(select 1)
(sync-in-context 2)
......
......@@ -10,7 +10,7 @@
[metabase.db :as db]
[metabase.email.messages :as email]
[metabase.events :as events]
(metabase.models [user :refer [User set-user-password! set-user-password-reset-token!]]
(metabase.models [user :refer [User set-user-password! set-user-password-reset-token!], :as user]
[session :refer [Session]]
[setting :refer [defsetting], :as setting])
[metabase.util :as u]
......@@ -164,14 +164,9 @@
(defn- google-auth-create-new-user! [first-name last-name email]
(check-autocreate-user-allowed-for-email email)
(db/insert! User
:first_name first-name
:last_name last-name
:email email
:google_auth true
;; just give the user a random password; they can go reset it if they ever change their mind and want to log in without Google Auth;
;; this lets us keep the NOT NULL constraints on password / salt without having to make things hairy and only enforce those for non-Google Auth users
:password (str (java.util.UUID/randomUUID))))
;; this will just give the user a random password; they can go reset it if they ever change their mind and want to log in without Google Auth;
;; this lets us keep the NOT NULL constraints on password / salt without having to make things hairy and only enforce those for non-Google Auth users
(user/create-user! first-name last-name email, :send-welcome false, :google-auth? true))
(defn- google-auth-fetch-or-create-user! [first-name last-name email]
(if-let [user (or (db/select-one [User :id :last_login] :email email)
......
(ns metabase.email
(:require [clojure.string :as s]
[clojure.tools.logging :as log]
[postal.core :as postal]
[postal.support :refer [make-props]]
(postal [core :as postal]
[support :refer [make-props]])
[metabase.models.setting :refer [defsetting] :as setting]
[metabase.util :as u])
(:import [javax.mail Session]))
(:import javax.mail.Session))
;; ## CONFIG
......@@ -31,12 +31,15 @@
(defn send-message
"Send an email to one or more RECIPIENTS.
RECIPIENTS is a sequence of email addresses; MESSAGE-TYPE must be either `:text` or `:html`.
(email/send-message
:subject \"[Metabase] Password Reset Request\"
:recipients [\"cam@metabase.com\"]
:message-type :text
:message \"How are you today?\")
:subject \"[Metabase] Password Reset Request\"
:recipients [\"cam@metabase.com\"]
:message-type :text
:message \"How are you today?\")
Upon success, this returns the MESSAGE that was just sent."
{:style/indent 0}
[& {:keys [subject recipients message-type message]}]
{:pre [(string? subject)
(sequential? recipients)
......
(ns metabase.email.messages
"Convenience functions for sending templated email messages. Each function here should represent a single email.
NOTE: we want to keep this about email formatting, so don't put heavy logic here RE: building data for emails."
(:require [hiccup.core :refer [html]]
(:require [clojure.core.cache :as cache]
[hiccup.core :refer [html]]
[medley.core :as m]
[stencil.core :as stencil]
[metabase.email :as email]
(stencil [core :as stencil]
[loader :as stencil-loader])
(metabase [config :as config]
[email :as email])
[metabase.models.setting :as setting]
[metabase.pulse.render :as render]
[metabase.util :as u]
(metabase.util [quotation :as quotation]
[urls :as url])))
;; NOTE: uncomment this in development to disable template caching
;; (stencil.loader/set-cache (clojure.core.cache/ttl-cache-factory {} :ttl 0))
;; Dev only -- disable template caching
(when config/is-dev?
(stencil-loader/set-cache (cache/ttl-cache-factory {} :ttl 0)))
;;; ### Public Interface
(defn send-new-user-email
"Format and Send an welcome email for newly created users."
"Format and send an welcome email for newly created users."
[invited invitor join-url]
(let [data-quote (quotation/random-quote)
company (or (setting/get :site-name) "Unknown")
message-body (stencil/render-file "metabase/email/new_user_invite"
{:emailType "new_user_invite"
:invitedName (:first_name invited)
:invitorName (:first_name invitor)
:invitorEmail (:email invitor)
:company company
:joinUrl join-url
:quotation (:quote data-quote)
:quotationAuthor (:author data-quote)
:today (u/format-date "MMM' 'dd,' 'yyyy" (System/currentTimeMillis))
:logoHeader true})]
{:emailType "new_user_invite"
:invitedName (:first_name invited)
:invitorName (:first_name invitor)
:invitorEmail (:email invitor)
:company company
:joinUrl join-url
:quotation (:quote data-quote)
:quotationAuthor (:author data-quote)
:today (u/format-date "MMM' 'dd,' 'yyyy")
:logoHeader true})]
(email/send-message
:subject (str "You're invited to join " company "'s Metabase")
:recipients [(:email invited)]
:message-type :html
:message message-body)))
(defn send-user-joined-admin-notification-email
"Send an email to the admin of this Metabase instance letting them know a new user joined."
[new-user invitor google-auth?]
{:pre [(map? new-user)
(m/boolean? google-auth?)
(or google-auth?
(and (map? invitor)
(u/is-email? (:email invitor))))]}
(let [data-quote (quotation/random-quote)]
(email/send-message
:subject (str "You're invited to join " company "'s Metabase")
:recipients [(:email invited)]
:message-type :html
:message message-body)))
:subject (format (if google-auth?
"%s created a Metabase account"
"%s accepted your Metabase invite")
(:common_name new-user))
:recipients [(if google-auth?
(setting/get :admin-email)
(:email invitor))]
:message-type :html
:message (stencil/render-file "metabase/email/user_joined_notification"
{:logoHeader true
:quotation (:quote data-quote)
:quotationAuthor (:author data-quote)
:joinedUserName (:first_name new-user)
:joinedViaSSO google-auth?
:joinedUserEmail (:email new-user)
:joinedDate (u/format-date "hh:mm") ; TODO - is this what we want?
:invitorEmail (:email invitor)
:joinedUserEditUrl (str (setting/get :-site-url) "/admin/people")}))))
(defn send-password-reset-email
"Format and Send an email informing the user how to reset their password."
"Format and send an email informing the user how to reset their password."
[email google-auth? hostname password-reset-url]
{:pre [(string? email)
(m/boolean? google-auth?)
......@@ -47,19 +83,20 @@
(string? hostname)
(string? password-reset-url)]}
(let [message-body (stencil/render-file "metabase/email/password_reset"
{:emailType "password_reset"
:hostname hostname
:sso google-auth?
:passwordResetUrl password-reset-url
:logoHeader true})]
{:emailType "password_reset"
:hostname hostname
:sso google-auth?
:passwordResetUrl password-reset-url
:logoHeader true})]
(email/send-message
:subject "[Metabase] Password Reset Request"
:recipients [email]
:message-type :html
:message message-body)))
:subject "[Metabase] Password Reset Request"
:recipients [email]
:message-type :html
:message message-body)))
(defn send-notification-email
"Format and Send an email informing the user about changes to objects in the system."
"Format and send an email informing the user about changes to objects in the system."
[email context]
{:pre [(string? email)
(u/is-email? email)
......@@ -91,8 +128,9 @@
:message-type :html
:message message-body)))
(defn send-follow-up-email
"Format and Send an email to the system admin following up on the installation."
"Format and send an email to the system admin following up on the installation."
[email msg-type]
{:pre [(string? email)
(u/is-email? email)
......@@ -119,6 +157,7 @@
:message-type :html
:message message-body)))
;; HACK: temporary workaround to postal requiring a file as the attachment
(defn- write-byte-array-to-temp-file
[^bytes img-bytes]
......@@ -145,14 +184,14 @@
(render/render-pulse-section result)))))
data-quote (quotation/random-quote)
message-body (stencil/render-file "metabase/email/pulse"
{:emailType "pulse"
:pulse (html body)
:pulseName (:name pulse)
:sectionStyle render/section-style
:colorGrey4 render/color-gray-4
:quotation (:quote data-quote)
:quotationAuthor (:author data-quote)
:logoFooter true})]
{:emailType "pulse"
:pulse (html body)
:pulseName (:name pulse)
:sectionStyle render/section-style
:colorGrey4 render/color-gray-4
:quotation (:quote data-quote)
:quotationAuthor (:author data-quote)
:logoFooter true})]
(apply vector {:type "text/html; charset=utf-8" :content message-body}
(map-indexed (fn [idx bytes] {:type :inline
:content-id (str "IMAGE" idx)
......
......@@ -3,24 +3,24 @@
<div style="padding-bottom: 1em;">
<h2 style="font-weight: normal; color: #4C545B;line-height: 1.65rem;">
{{joinedUserName}}
{{#jonedViaSSO}}created a Metabase account.{{/joinedViaSSO}}
{{#joinedViaSSO}}created a Metabase account.{{/joinedViaSSO}}
{{^joinedViaSSO}}accepted your Metabase invitation.{{/joinedViaSSO}}
</h2>
<h4 style="font-weight: normal;">
<a style="color: #4A90E2; text-decoration: none;" href="mailto:{{invitorEmail}}">
{{joinedUserEmail}}
</a>
joined {{#sso}}via Google Auth{{/sso}} at {{joinedDate}}
joined {{#joinedViaSSO}}via Google Auth{{/joinedViaSSO}} at {{joinedDate}}
</h4>
</div>
<div style="padding: 1em;">
<a style="display: inline-block; box-sizing: border-box; text-decoration: none; font-size: 1.063rem; padding: 0.5rem 1.375rem; background: #FBFCFD; border: 1px solid #ddd; color: #444; cursor: pointer; text-decoration: none; font-weight: bold; border-radius: 4px; background-color: #4990E2; border-color: #4990E2; color: #fff;" href="{{joinedUserEditUrl}}">
Edit {{joinUserName}}'s settings
Edit {{joinedUserName}}'s settings
</a>
</div>
<div style="padding-bottom: 2em; font-size: x-small;">
Button not working? Paste this link into your browser:<br/>
{{joinUrl}}
{{joinedUserEditUrl}}
</div>
</div>
{{> metabase/email/_footer }}
......@@ -70,27 +70,28 @@
(declare form-password-reset-url
set-user-password-reset-token!)
;; TODO - `:send-welcome?` instead of `:send-welcome`
(defn create-user!
"Convenience function for creating a new `User` and sending out the welcome email."
[first-name last-name email-address & {:keys [send-welcome invitor password]
:or {send-welcome false}}]
{:pre [(string? first-name)
(string? last-name)
(string? email-address)]}
(when-let [new-user (db/insert! User
:email email-address
:first_name first-name
:last_name last-name
:password (if-not (nil? password)
password
(str (java.util.UUID/randomUUID))))]
[first-name last-name email-address & {:keys [send-welcome invitor password google-auth?]
:or {send-welcome false
google-auth? false}}]
{:pre [(string? first-name) (string? last-name) (string? email-address)]}
(u/prog1 (db/insert! User
:email email-address
:first_name first-name
:last_name last-name
:password (if-not (nil? password)
password
(str (java.util.UUID/randomUUID)))
:google_auth google-auth?)
(when send-welcome
(let [reset-token (set-user-password-reset-token! (:id new-user))
;; NOTE: the new user join url is just a password reset with an indicator that this is a first time user
(let [reset-token (set-user-password-reset-token! (:id <>))
;; the new user join url is just a password reset with an indicator that this is a first time user
join-url (str (form-password-reset-url reset-token) "#new")]
(email/send-new-user-email new-user invitor join-url)))
;; return the newly created user
new-user))
(email/send-new-user-email <> invitor join-url)))
;; notifiy the admin of this MB instance that a new user has joined (TODO - are there cases where we *don't* want to do this)
(email/send-user-joined-admin-notification-email <> invitor google-auth?)))
(defn set-user-password!
"Updates the stored password for a specified `User` by hashing the password with a random salt."
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment