support add pwless login

This commit is contained in:
Elio Bischof
2023-09-26 19:45:19 +02:00
parent ca4b141217
commit b53b8158b7
2 changed files with 322 additions and 96 deletions

View File

@@ -31,19 +31,20 @@ import (
)
const (
logoURL = "logo.png"
orgID = "org1"
policyID = "policy1"
userID = "user1"
loginName = "loginName1"
orgID = "org1"
codeID = "event1"
logoURL = "logo.png"
eventOrigin = "https://triggered.here"
assetsPath = "/assets/v1"
loginName = "loginName1"
lastEmail = "last@email.com"
instancePrimaryDomain = "primary.domain"
externalDomain = "external.domain"
externalPort = 3000
externalSecure = false
externalProtocol = "http"
lastEmail = "last@email.com"
)
func Test_userNotifier_reduceInitCodeAdded(t *testing.T) {
@@ -645,6 +646,313 @@ func Test_userNotifier_reducePasswordCodeAdded(t *testing.T) {
}
}
func Test_userNotifier_reduceDomainClaimed(t *testing.T) {
expectMailSubject := "Domain has been claimed"
tests := []struct {
name string
test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want)
}{{
name: "asset url with event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
}, args{
event: &user.DomainClaimedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
TriggeredAtOrigin: eventOrigin,
},
}, w
},
}, {
name: "asset url without event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
}, args{
event: &user.DomainClaimedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
},
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
_, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceDomainClaimed(a.event)
if w.err != nil {
w.err(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func Test_userNotifier_reducePasswordlessCodeRequested(t *testing.T) {
expectMailSubject := "Add Passwordless Login"
tests := []struct {
name string
test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want)
}{{
name: "asset url with event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, "testcode")
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanPasswordlessInitCodeRequestedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
ID: codeID,
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
TriggeredAtOrigin: eventOrigin,
},
}, w
},
}, {
name: "asset url without event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, "testcode")
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanPasswordlessInitCodeRequestedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
ID: codeID,
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
},
}, w
},
}, {
name: "button url with event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
codeAlg, code := cryptoValue(t, ctrl, testCode)
expectContent := fmt.Sprintf("%s/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", eventOrigin, userID, orgID, codeID, testCode)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
userDataCrypto: codeAlg,
SMSTokenCrypto: nil,
}, args{
event: &user.HumanPasswordlessInitCodeRequestedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
ID: codeID,
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
TriggeredAtOrigin: eventOrigin,
},
}, w
},
}, {
name: "button url without event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
testCode := "testcode"
codeAlg, code := cryptoValue(t, ctrl, testCode)
expectContent := fmt.Sprintf("%s://%s:%d/ui/login/login/passwordless/init?userID=%s&orgID=%s&codeID=%s&code=%s", externalProtocol, instancePrimaryDomain, externalPort, userID, orgID, codeID, testCode)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
userDataCrypto: codeAlg,
}, args{
event: &user.HumanPasswordlessInitCodeRequestedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
ID: codeID,
Code: code,
Expiry: time.Hour,
URLTemplate: "",
CodeReturned: false,
},
}, w
},
}, {
name: "button url with url template and event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.URL}}"
urlTemplate := "https://my.custom.url/org/{{.OrgID}}/user/{{.UserID}}/verify/{{.Code}}"
testCode := "testcode"
expectContent := fmt.Sprintf("https://my.custom.url/org/%s/user/%s/verify/%s", orgID, userID, testCode)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
codeAlg, code := cryptoValue(t, ctrl, testCode)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().HumanPasswordlessInitCodeSent(gomock.Any(), userID, orgID, codeID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
userDataCrypto: codeAlg,
SMSTokenCrypto: nil,
}, args{
event: &user.HumanPasswordlessInitCodeRequestedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
ID: codeID,
Code: code,
Expiry: time.Hour,
URLTemplate: urlTemplate,
CodeReturned: false,
TriggeredAtOrigin: eventOrigin,
},
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
_, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reducePasswordlessCodeRequested(a.event)
if w.err != nil {
w.err(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func Test_userNotifier_reducePasswordChanged(t *testing.T) {
expectMailSubject := "Password of user has changed"
tests := []struct {
@@ -738,93 +1046,6 @@ func Test_userNotifier_reducePasswordChanged(t *testing.T) {
}
}
func Test_userNotifier_reduceDomainClaimed(t *testing.T) {
expectMailSubject := "Domain has been claimed"
tests := []struct {
name string
test func(*gomock.Controller, *mock.MockQueries, *mock.MockCommands) (fields, args, want)
}{{
name: "asset url with event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s%s/%s/%s", eventOrigin, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
}, args{
event: &user.DomainClaimedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
TriggeredAtOrigin: eventOrigin,
},
}, w
},
}, {
name: "asset url without event trigger url",
test: func(ctrl *gomock.Controller, queries *mock.MockQueries, commands *mock.MockCommands) (f fields, a args, w want) {
givenTemplate := "{{.LogoURL}}"
expectContent := fmt.Sprintf("%s://%s:%d%s/%s/%s", externalProtocol, instancePrimaryDomain, externalPort, assetsPath, policyID, logoURL)
w.message = messages.Email{
Recipients: []string{lastEmail},
Subject: expectMailSubject,
Content: expectContent,
}
queries.EXPECT().SearchInstanceDomains(gomock.Any(), gomock.Any()).Return(&query.InstanceDomains{
Domains: []*query.InstanceDomain{{
Domain: instancePrimaryDomain,
IsPrimary: true,
}},
}, nil)
expectTemplateQueries(queries, givenTemplate)
commands.EXPECT().UserDomainClaimedSent(gomock.Any(), orgID, userID).Return(nil)
return fields{
queries: queries,
commands: commands,
es: eventstore.NewEventstore(eventstore.TestConfig(
es_repo_mock.NewRepo(t).ExpectFilterEvents(),
)),
}, args{
event: &user.DomainClaimedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(&repository.Event{
AggregateID: userID,
ResourceOwner: sql.NullString{String: orgID},
CreationDate: time.Now().UTC(),
}),
},
}, w
},
}}
fs, err := statik_fs.NewWithNamespace("notification")
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
queries := mock.NewMockQueries(ctrl)
commands := mock.NewMockCommands(ctrl)
f, a, w := tt.test(ctrl, queries, commands)
_, err = newUserNotifier(t, ctrl, queries, fs, f, a, w).reduceDomainClaimed(a.event)
if w.err != nil {
w.err(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
type fields struct {
queries *mock.MockQueries
commands *mock.MockCommands

View File

@@ -317,11 +317,12 @@ func HumanPasswordlessInitCodeAddedEventMapper(event *repository.Event) (eventst
type HumanPasswordlessInitCodeRequestedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
ID string `json:"id"`
Code *crypto.CryptoValue `json:"code"`
Expiry time.Duration `json:"expiry"`
URLTemplate string `json:"url_template,omitempty"`
CodeReturned bool `json:"code_returned,omitempty"`
TriggeredAtOrigin string `json:"base_url,omitempty"`
}
func (e *HumanPasswordlessInitCodeRequestedEvent) Data() interface{} {
@@ -332,6 +333,10 @@ func (e *HumanPasswordlessInitCodeRequestedEvent) UniqueConstraints() []*eventst
return nil
}
func (e *HumanPasswordlessInitCodeRequestedEvent) TriggerOrigin() string {
return e.TriggeredAtOrigin
}
func NewHumanPasswordlessInitCodeRequestedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,