Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
095aff64
Commit
095aff64
authored
Oct 30, 2015
by
bobbyrullo
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #168 from bobbyrullo/invite_emails
Invite emails
parents
9172f54f
d1e292eb
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
99 additions
and
8 deletions
+99
-8
mailgun_test.go
email/mailgun_test.go
+0
-1
template.go
email/template.go
+9
-0
template_test.go
email/template_test.go
+21
-0
user_api_test.go
integration/user_api_test.go
+16
-0
config.go
server/config.go
+5
-2
password_test.go
server/password_test.go
+2
-2
invite.html
static/email/invite.html
+7
-0
invite.txt
static/email/invite.txt
+4
-0
test
test
+1
-1
api.go
user/api/api.go
+2
-1
api_test.go
user/api/api_test.go
+11
-0
email.go
user/email/email.go
+21
-1
No files found.
email/mailgun_test.go
View file @
095aff64
...
...
@@ -16,7 +16,6 @@ func TestNewEmailConfigFromReader(t *testing.T) {
{
json
:
`{"type":"mailgun","id":"mg","privateAPIKey":"private","publicAPIKey":"public","domain":"example.com"}`
,
want
:
MailgunEmailerConfig
{
ID
:
"mg"
,
PrivateAPIKey
:
"private"
,
PublicAPIKey
:
"public"
,
Domain
:
"example.com"
,
...
...
email/template.go
View file @
095aff64
...
...
@@ -36,6 +36,11 @@ type TemplatizedEmailer struct {
textTemplates
*
template
.
Template
htmlTemplates
*
htmltemplate
.
Template
emailer
Emailer
globalCtx
map
[
string
]
interface
{}
}
func
(
t
*
TemplatizedEmailer
)
SetGlobalContext
(
ctx
map
[
string
]
interface
{})
{
t
.
globalCtx
=
ctx
}
// SendMail queues an email to be sent to a recipient.
...
...
@@ -59,6 +64,10 @@ func (t *TemplatizedEmailer) SendMail(from, subject, tplName string, data map[st
data
[
"from"
]
=
from
data
[
"subject"
]
=
subject
for
k
,
v
:=
range
t
.
globalCtx
{
data
[
k
]
=
v
}
var
textBuffer
bytes
.
Buffer
if
textTpl
!=
nil
{
err
:=
textTpl
.
Execute
(
&
textBuffer
,
data
)
...
...
email/template_test.go
View file @
095aff64
...
...
@@ -9,9 +9,11 @@ import (
const
(
textTemplateString
=
`{{define "T1.txt"}}{{.gift}} from {{.from}} to {{.to}}.{{end}}
{{define "T3.txt"}}Hello there, {{.name}}!{{end}}
{{define "T4.txt"}}Hello there, {{.name}}! Welcome to {{.planet}}!{{end}}
`
htmlTemplateString
=
`{{define "T1.html"}}<html><body>{{.gift}} from {{.from}} to {{.to}}.</body></html>{{end}}
{{define "T2.html"}}<html><body>Hello, {{.name}}!</body></html>{{end}}
{{define "T4.html"}}<html><body>Hello there, {{.name}}! Welcome to {{.planet}}!</body></html>{{end}}
`
)
...
...
@@ -51,6 +53,7 @@ func TestTemplatizedEmailSendMail(t *testing.T) {
wantText
string
wantHtml
string
wantErr
bool
ctx
map
[
string
]
interface
{}
}{
{
tplName
:
"T1"
,
...
...
@@ -97,11 +100,29 @@ func TestTemplatizedEmailSendMail(t *testing.T) {
wantText
:
""
,
wantHtml
:
htmlStart
+
"Hello, Alice<script>alert('hacked!')</script>!"
+
htmlEnd
,
},
{
tplName
:
"T4"
,
from
:
"bob@example.com"
,
to
:
"alice@example.com"
,
subject
:
"hello there"
,
data
:
map
[
string
]
interface
{}{
"name"
:
"Alice"
,
},
wantText
:
"Hello there, Alice! Welcome to Mars!"
,
ctx
:
map
[
string
]
interface
{}{
"planet"
:
"Mars"
,
},
wantHtml
:
"<html><body>Hello there, Alice! Welcome to Mars!</body></html>"
,
},
}
for
i
,
tt
:=
range
tests
{
emailer
:=
&
testEmailer
{}
templatizer
:=
NewTemplatizedEmailerFromTemplates
(
textTemplates
,
htmlTemplates
,
emailer
)
if
tt
.
ctx
!=
nil
{
templatizer
.
SetGlobalContext
(
tt
.
ctx
)
}
err
:=
templatizer
.
SendMail
(
tt
.
from
,
tt
.
subject
,
tt
.
tplName
,
tt
.
data
,
tt
.
to
)
if
tt
.
wantErr
{
if
err
==
nil
{
...
...
integration/user_api_test.go
View file @
095aff64
...
...
@@ -519,6 +519,7 @@ func TestCreateUser(t *testing.T) {
cantEmail
:
tt
.
cantEmail
,
lastEmail
:
tt
.
req
.
User
.
Email
,
lastClientID
:
"XXX"
,
lastWasInvite
:
true
,
lastRedirectURL
:
*
urlParsed
,
}
if
diff
:=
pretty
.
Compare
(
wantEmalier
,
f
.
emailer
);
diff
!=
""
{
...
...
@@ -578,6 +579,7 @@ type testEmailer struct {
lastEmail
string
lastClientID
string
lastRedirectURL
url
.
URL
lastWasInvite
bool
}
// SendResetPasswordEmail returns resetPasswordURL when it can't email, mimicking the behavior of the real UserEmailer.
...
...
@@ -585,6 +587,20 @@ func (t *testEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
t
.
lastEmail
=
email
t
.
lastRedirectURL
=
redirectURL
t
.
lastClientID
=
clientID
t
.
lastWasInvite
=
false
var
retURL
*
url
.
URL
if
t
.
cantEmail
{
retURL
=
&
testResetPasswordURL
}
return
retURL
,
nil
}
func
(
t
*
testEmailer
)
SendInviteEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
)
(
*
url
.
URL
,
error
)
{
t
.
lastEmail
=
email
t
.
lastRedirectURL
=
redirectURL
t
.
lastClientID
=
clientID
t
.
lastWasInvite
=
true
var
retURL
*
url
.
URL
if
t
.
cantEmail
{
...
...
server/config.go
View file @
095aff64
...
...
@@ -85,7 +85,7 @@ func (cfg *ServerConfig) Server() (*Server, error) {
return
nil
,
err
}
err
=
setEmailer
(
&
srv
,
cfg
.
EmailFromAddress
,
cfg
.
EmailerConfigFile
,
cfg
.
EmailTemplateDirs
)
err
=
setEmailer
(
&
srv
,
cfg
.
IssuerName
,
cfg
.
EmailFromAddress
,
cfg
.
EmailerConfigFile
,
cfg
.
EmailTemplateDirs
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -238,7 +238,7 @@ func setTemplates(srv *Server, tpls *template.Template) error {
return
nil
}
func
setEmailer
(
srv
*
Server
,
fromAddress
,
emailerConfigFile
string
,
emailTemplateDirs
[]
string
)
error
{
func
setEmailer
(
srv
*
Server
,
issuerName
,
fromAddress
,
emailerConfigFile
string
,
emailTemplateDirs
[]
string
)
error
{
cfg
,
err
:=
email
.
NewEmailerConfigFromFile
(
emailerConfigFile
)
if
err
!=
nil
{
...
...
@@ -290,6 +290,9 @@ func setEmailer(srv *Server, fromAddress, emailerConfigFile string, emailTemplat
}
}
tMailer
:=
email
.
NewTemplatizedEmailerFromTemplates
(
textTemplates
,
htmlTemplates
,
emailer
)
tMailer
.
SetGlobalContext
(
map
[
string
]
interface
{}{
"issuer_name"
:
issuerName
,
})
ue
:=
useremail
.
NewUserEmailer
(
srv
.
UserRepo
,
srv
.
PasswordInfoRepo
,
...
...
server/password_test.go
View file @
095aff64
...
...
@@ -102,7 +102,7 @@ func TestSendResetPasswordEmailHandler(t *testing.T) {
wantEmailer
:
&
testEmailer
{
to
:
str
(
"Email-1@example.com"
),
from
:
"noreply@example.com"
,
subject
:
"Reset
your password.
"
,
subject
:
"Reset
Your Password
"
,
},
wantPRUserID
:
"ID-1"
,
wantPRRedirect
:
&
testRedirectURL
,
...
...
@@ -138,7 +138,7 @@ func TestSendResetPasswordEmailHandler(t *testing.T) {
wantEmailer
:
&
testEmailer
{
to
:
str
(
"Email-1@example.com"
),
from
:
"noreply@example.com"
,
subject
:
"Reset
your password.
"
,
subject
:
"Reset
Your Password
"
,
},
wantPRPassword
:
"password"
,
wantPRUserID
:
"ID-1"
,
...
...
static/email/invite.html
0 → 100644
View file @
095aff64
<html>
<body>
Welcome to Dex! Click below to set your password:
<a
href=
"{{ .link }}"
>
Set Password
</a>
</body>
</html>
static/email/invite.txt
0 → 100644
View file @
095aff64
Welcome to Dex! Click below to set your password:
Link:
{{ .link }}
test
View file @
095aff64
...
...
@@ -14,7 +14,7 @@ COVER=${COVER:-"-cover"}
source
./build
TESTABLE
=
"connector db integration pkg/crypto pkg/flag pkg/http pkg/net pkg/time pkg/html functional/repo server session user user/api"
TESTABLE
=
"connector db integration pkg/crypto pkg/flag pkg/http pkg/net pkg/time pkg/html functional/repo server session user user/api
email
"
FORMATTABLE
=
"
$TESTABLE
cmd/dexctl cmd/dex-worker cmd/dex-overlord examples/app functional pkg/log"
# user has not provided PKG override
...
...
user/api/api.go
View file @
095aff64
...
...
@@ -89,6 +89,7 @@ type UsersAPI struct {
type
Emailer
interface
{
SendResetPasswordEmail
(
string
,
url
.
URL
,
string
)
(
*
url
.
URL
,
error
)
SendInviteEmail
(
string
,
url
.
URL
,
string
)
(
*
url
.
URL
,
error
)
}
type
Creds
struct
{
...
...
@@ -169,7 +170,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
usr
=
userToSchemaUser
(
userUser
)
url
,
err
:=
u
.
emailer
.
Send
ResetPassword
Email
(
usr
.
Email
,
validRedirURL
,
creds
.
ClientID
)
url
,
err
:=
u
.
emailer
.
Send
Invite
Email
(
usr
.
Email
,
validRedirURL
,
creds
.
ClientID
)
// An email is sent only if we don't get a link and there's no error.
emailSent
:=
err
==
nil
&&
url
==
nil
...
...
user/api/api_test.go
View file @
095aff64
...
...
@@ -20,13 +20,23 @@ type testEmailer struct {
lastEmail
string
lastClientID
string
lastRedirectURL
url
.
URL
lastWasInvite
bool
}
// SendResetPasswordEmail returns resetPasswordURL when it can't email, mimicking the behavior of the real UserEmailer.
func
(
t
*
testEmailer
)
SendResetPasswordEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
)
(
*
url
.
URL
,
error
)
{
return
t
.
sendEmail
(
email
,
redirectURL
,
clientID
,
false
)
}
func
(
t
*
testEmailer
)
SendInviteEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
)
(
*
url
.
URL
,
error
)
{
return
t
.
sendEmail
(
email
,
redirectURL
,
clientID
,
true
)
}
func
(
t
*
testEmailer
)
sendEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
,
invite
bool
)
(
*
url
.
URL
,
error
)
{
t
.
lastEmail
=
email
t
.
lastRedirectURL
=
redirectURL
t
.
lastClientID
=
clientID
t
.
lastWasInvite
=
invite
var
retURL
*
url
.
URL
if
t
.
cantEmail
{
...
...
@@ -369,6 +379,7 @@ func TestCreateUser(t *testing.T) {
lastEmail
:
tt
.
usr
.
Email
,
lastClientID
:
tt
.
creds
.
ClientID
,
lastRedirectURL
:
tt
.
redirURL
,
lastWasInvite
:
true
,
}
if
diff
:=
pretty
.
Compare
(
wantEmalier
,
emailer
);
diff
!=
""
{
t
.
Errorf
(
"case %d: Compare(want, got) = %v"
,
i
,
...
...
user/email/email.go
View file @
095aff64
...
...
@@ -53,6 +53,16 @@ func NewUserEmailer(ur user.UserRepo,
// This method DOES NOT check for client ID, redirect URL validity - it is expected that upstream users have already done so.
// If there is no emailer is configured, the URL of the aforementioned link is returned, otherwise nil is returned.
func
(
u
*
UserEmailer
)
SendResetPasswordEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
)
(
*
url
.
URL
,
error
)
{
return
u
.
sendResetPasswordOrInviteEmail
(
email
,
redirectURL
,
clientID
,
false
)
}
// SendInviteEmail is exactly the same as SendResetPasswordEmail, except that it uses the invite template and subject name.
// In the near future, invite emails might diverge further.
func
(
u
*
UserEmailer
)
SendInviteEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
)
(
*
url
.
URL
,
error
)
{
return
u
.
sendResetPasswordOrInviteEmail
(
email
,
redirectURL
,
clientID
,
true
)
}
func
(
u
*
UserEmailer
)
sendResetPasswordOrInviteEmail
(
email
string
,
redirectURL
url
.
URL
,
clientID
string
,
invite
bool
)
(
*
url
.
URL
,
error
)
{
usr
,
err
:=
u
.
ur
.
GetByEmail
(
nil
,
email
)
if
err
==
user
.
ErrorNotFound
{
log
.
Errorf
(
"No Such user for email: %q"
,
email
)
...
...
@@ -95,8 +105,17 @@ func (u *UserEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
q
.
Set
(
"token"
,
token
)
resetURL
.
RawQuery
=
q
.
Encode
()
var
tmplName
,
subj
string
if
invite
{
tmplName
=
"invite"
subj
=
"Activate Your Account"
}
else
{
tmplName
=
"password-reset"
subj
=
"Reset Your Password"
}
if
u
.
emailer
!=
nil
{
err
=
u
.
emailer
.
SendMail
(
u
.
fromAddress
,
"Reset your password."
,
"password-reset"
,
err
=
u
.
emailer
.
SendMail
(
u
.
fromAddress
,
subj
,
tmplName
,
map
[
string
]
interface
{}{
"email"
:
usr
.
Email
,
"link"
:
resetURL
.
String
(),
...
...
@@ -107,6 +126,7 @@ func (u *UserEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
return
nil
,
err
}
return
&
resetURL
,
nil
}
// SendEmailVerification sends an email to the user with the given userID containing a link which when visited marks the user as having had their email verified.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment