Skip to content

Commit 4c5741e

Browse files
authored
Merge branch 'main' into change-search-column-types
2 parents bb20e60 + 7c88c25 commit 4c5741e

File tree

29 files changed

+290
-178
lines changed

29 files changed

+290
-178
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ If you do self-host and enjoy Fider, please [let us know where you're using it](
4141

4242
# 💰 Donations and Sponsors
4343

44-
Supoprt the development of Fider to help us make it the best feedback tool! You can set up donations as small or large as you want to help us keep Fider going. [Donate](https://opencollective.com/fider)
44+
Support the development of Fider to help us make it the best feedback tool! You can set up donations as small or large as you want to help us keep Fider going. [Donate](https://opencollective.com/fider)
4545

4646
If your organization uses Fider, consider becoming a sponsor - set up a monthly donation and get your logo and link on the README. [Become a sponsor](https://opencollective.com/fider)
4747

app/actions/signin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import (
1616
type SignInByEmail struct {
1717
Email string `json:"email" format:"lower"`
1818
VerificationCode string
19+
LinkKey string
1920
}
2021

2122
func NewSignInByEmail() *SignInByEmail {
2223
return &SignInByEmail{
2324
VerificationCode: entity.GenerateEmailVerificationCode(),
25+
LinkKey: entity.GenerateEmailVerificationKey(),
2426
}
2527
}
2628

@@ -118,11 +120,13 @@ type SignInByEmailWithName struct {
118120
Email string `json:"email" format:"lower"`
119121
Name string `json:"name"`
120122
VerificationCode string
123+
LinkKey string
121124
}
122125

123126
func NewSignInByEmailWithName() *SignInByEmailWithName {
124127
return &SignInByEmailWithName{
125128
VerificationCode: entity.GenerateEmailVerificationCode(),
129+
LinkKey: entity.GenerateEmailVerificationKey(),
126130
}
127131
}
128132

app/handlers/admin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ func AdvancedSettingsPage() web.HandlerFunc {
3636
Page: "Administration/pages/AdvancedSettings.page",
3737
Title: "Advanced · Site Settings",
3838
Data: web.Map{
39-
"customCSS": c.Tenant().CustomCSS,
40-
"allowedSchemes": c.Tenant().AllowedSchemes,
41-
"licenseKey": billingState.Result.LicenseKey,
42-
"isCommercial": c.Tenant().IsCommercial,
39+
"customCSS": c.Tenant().CustomCSS,
40+
"allowedSchemes": c.Tenant().AllowedSchemes,
41+
"licenseKey": billingState.Result.LicenseKey,
42+
"hasCommercialFeatures": c.Tenant().HasCommercialFeatures,
4343
},
4444
})
4545
}

app/handlers/billing.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func ManageBilling() web.HandlerFunc {
2727
Data: web.Map{
2828
"stripeCustomerID": billingState.Result.CustomerID,
2929
"stripeSubscriptionID": billingState.Result.SubscriptionID,
30-
"licenseKey": billingState.Result.LicenseKey,
31-
"paddleSubscriptionID": billingState.Result.PaddleSubscriptionID,
32-
"isCommercial": c.Tenant().IsCommercial,
30+
"licenseKey": billingState.Result.LicenseKey,
31+
"paddleSubscriptionID": billingState.Result.PaddleSubscriptionID,
32+
"hasCommercialFeatures": c.Tenant().HasCommercialFeatures,
3333
},
3434
})
3535
}

app/handlers/signin.go

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,16 @@ func SignInByEmail() web.HandlerFunc {
8787
// Only send code if user exists
8888
if userExists {
8989
err := bus.Dispatch(c, &cmd.SaveVerificationKey{
90-
Key: action.VerificationCode,
90+
Key: action.LinkKey,
91+
Code: action.VerificationCode,
9192
Duration: 15 * time.Minute,
9293
Request: action,
9394
})
9495
if err != nil {
9596
return c.Failure(err)
9697
}
9798

98-
c.Enqueue(tasks.SendSignInEmail(action.Email, action.VerificationCode))
99+
c.Enqueue(tasks.SendSignInEmail(action.Email, action.LinkKey, action.VerificationCode))
99100
}
100101

101102
return c.Ok(web.Map{
@@ -129,15 +130,16 @@ func SignInByEmailWithName() web.HandlerFunc {
129130

130131
// Save verification with name
131132
err = bus.Dispatch(c, &cmd.SaveVerificationKey{
132-
Key: action.VerificationCode,
133+
Key: action.LinkKey,
134+
Code: action.VerificationCode,
133135
Duration: 15 * time.Minute,
134136
Request: action,
135137
})
136138
if err != nil {
137139
return c.Failure(err)
138140
}
139141

140-
c.Enqueue(tasks.SendSignInEmail(action.Email, action.VerificationCode))
142+
c.Enqueue(tasks.SendSignInEmail(action.Email, action.LinkKey, action.VerificationCode))
141143

142144
return c.Ok(web.Map{})
143145
}
@@ -169,18 +171,20 @@ func VerifySignInCode() web.HandlerFunc {
169171

170172
result := verification.Result
171173

172-
// Check if already verified (with grace period)
174+
// Code is single-use: reject if already verified
173175
if result.VerifiedAt != nil {
174-
if time.Since(*result.VerifiedAt) > 5*time.Minute {
175-
return c.Gone()
176-
}
177-
} else {
178-
// Check if expired
179-
if time.Now().After(result.ExpiresAt) {
180-
// Mark as verified to prevent reuse
181-
_ = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: action.Code})
182-
return c.Gone()
183-
}
176+
return c.BadRequest(web.Map{
177+
"code": "Invalid or expired verification code",
178+
})
179+
}
180+
181+
// Check if expired
182+
if time.Now().After(result.ExpiresAt) {
183+
// Mark as verified to prevent reuse
184+
_ = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: result.Key})
185+
return c.BadRequest(web.Map{
186+
"code": "Invalid or expired verification code",
187+
})
184188
}
185189

186190
// Check if user exists
@@ -207,7 +211,7 @@ func VerifySignInCode() web.HandlerFunc {
207211
}
208212

209213
// Mark code as verified
210-
err = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: action.Code})
214+
err = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: result.Key})
211215
if err != nil {
212216
return c.Failure(err)
213217
}
@@ -226,7 +230,7 @@ func VerifySignInCode() web.HandlerFunc {
226230
}
227231

228232
// Mark code as verified
229-
err = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: action.Code})
233+
err = bus.Dispatch(c, &cmd.SetKeyAsVerified{Key: result.Key})
230234
if err != nil {
231235
return c.Failure(err)
232236
}
@@ -254,7 +258,8 @@ func ResendSignInCode() web.HandlerFunc {
254258

255259
// Save new verification code
256260
err := bus.Dispatch(c, &cmd.SaveVerificationKey{
257-
Key: action.VerificationCode,
261+
Key: action.LinkKey,
262+
Code: action.VerificationCode,
258263
Duration: 15 * time.Minute,
259264
Request: action,
260265
})
@@ -263,7 +268,7 @@ func ResendSignInCode() web.HandlerFunc {
263268
}
264269

265270
// Send new email
266-
c.Enqueue(tasks.SendSignInEmail(action.Email, action.VerificationCode))
271+
c.Enqueue(tasks.SendSignInEmail(action.Email, action.LinkKey, action.VerificationCode))
267272

268273
return c.Ok(web.Map{})
269274
}

app/handlers/signin_test.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ func TestSignInByEmailHandler_ExistingUser(t *testing.T) {
5757
ExecutePost(handlers.SignInByEmail(), `{ "email": "jon.snow@got.com" }`)
5858

5959
Expect(code).Equals(http.StatusOK)
60-
Expect(saveKeyCmd.Key).HasLen(6)
60+
Expect(saveKeyCmd.Key).HasLen(64)
61+
Expect(saveKeyCmd.Code).HasLen(6)
6162
Expect(saveKeyCmd.Request.GetKind()).Equals(enum.EmailVerificationKindSignIn)
6263
Expect(saveKeyCmd.Request.GetEmail()).Equals("jon.snow@got.com")
6364
Expect(response.Body.String()).ContainsSubstring(`"userExists":true`)
@@ -98,7 +99,8 @@ func TestSignInByEmailWithNameHandler_NewUser(t *testing.T) {
9899
ExecutePost(handlers.SignInByEmailWithName(), `{ "email": "new.user@got.com", "name": "New User" }`)
99100

100101
Expect(code).Equals(http.StatusOK)
101-
Expect(saveKeyCmd.Key).HasLen(6)
102+
Expect(saveKeyCmd.Key).HasLen(64)
103+
Expect(saveKeyCmd.Code).HasLen(6)
102104
Expect(saveKeyCmd.Request.GetKind()).Equals(enum.EmailVerificationKindSignIn)
103105
Expect(saveKeyCmd.Request.GetEmail()).Equals("new.user@got.com")
104106
Expect(saveKeyCmd.Request.GetName()).Equals("New User")
@@ -820,7 +822,7 @@ func TestVerifySignInCodeHandler_ExpiredCode(t *testing.T) {
820822
OnTenant(mock.DemoTenant).
821823
ExecutePost(handlers.VerifySignInCode(), `{ "email": "jon.snow@got.com", "code": "123456" }`)
822824

823-
Expect(code).Equals(http.StatusGone)
825+
Expect(code).Equals(http.StatusBadRequest)
824826
}
825827

826828
func TestVerifySignInCodeHandler_CorrectCode_ExistingUser(t *testing.T) {
@@ -951,6 +953,29 @@ func TestVerifySignInCodeHandler_CorrectCode_NewUser_PrivateTenant(t *testing.T)
951953
Expect(code).Equals(http.StatusForbidden)
952954
}
953955

956+
func TestVerifySignInCodeHandler_AlreadyVerifiedCode_ShouldReject(t *testing.T) {
957+
RegisterT(t)
958+
959+
verifiedAt := time.Now().Add(-1 * time.Minute)
960+
bus.AddHandler(func(ctx context.Context, q *query.GetVerificationByEmailAndCode) error {
961+
q.Result = &entity.EmailVerification{
962+
Email: "jon.snow@got.com",
963+
Key: "some-long-link-key",
964+
CreatedAt: time.Now().Add(-10 * time.Minute),
965+
ExpiresAt: time.Now().Add(5 * time.Minute),
966+
VerifiedAt: &verifiedAt,
967+
}
968+
return nil
969+
})
970+
971+
server := mock.NewServer()
972+
code, _ := server.
973+
OnTenant(mock.DemoTenant).
974+
ExecutePost(handlers.VerifySignInCode(), `{ "email": "jon.snow@got.com", "code": "123456" }`)
975+
976+
Expect(code).Equals(http.StatusBadRequest)
977+
}
978+
954979
func TestResendSignInCodeHandler_ValidEmail(t *testing.T) {
955980
RegisterT(t)
956981

@@ -966,7 +991,8 @@ func TestResendSignInCodeHandler_ValidEmail(t *testing.T) {
966991
ExecutePost(handlers.ResendSignInCode(), `{ "email": "jon.snow@got.com" }`)
967992

968993
Expect(code).Equals(http.StatusOK)
969-
Expect(saveKeyCmd.Key).HasLen(6)
994+
Expect(saveKeyCmd.Key).HasLen(64)
995+
Expect(saveKeyCmd.Code).HasLen(6)
970996
Expect(saveKeyCmd.Request.GetKind()).Equals(enum.EmailVerificationKindSignIn)
971997
Expect(saveKeyCmd.Request.GetEmail()).Equals("jon.snow@got.com")
972998
}

app/models/cmd/tenant.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type ActivateTenant struct {
4747

4848
type SaveVerificationKey struct {
4949
Key string
50+
Code string
5051
Duration time.Duration
5152
Request NewEmailVerification
5253
}

app/models/entity/tenant.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ type Tenant struct {
2222
IsEmailAuthAllowed bool `json:"isEmailAuthAllowed"`
2323
IsFeedEnabled bool `json:"isFeedEnabled"`
2424
PreventIndexing bool `json:"preventIndexing"`
25-
IsModerationEnabled bool `json:"isModerationEnabled"`
26-
IsCommercial bool `json:"isCommercial"`
25+
IsModerationEnabled bool `json:"isModerationEnabled"`
26+
HasCommercialFeatures bool `json:"hasCommercialFeatures"`
2727
}
2828

2929
func (t *Tenant) IsDisabled() bool {

app/pkg/web/testdata/home_ssr.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h2 class="text-display2">Please enable JavaScript</h2>
4545

4646
<script id="server-data" type="application/json">
4747

48-
{"contextID":"CONTEXT_ID","description":"My Page Description","page":"Test.page","props":{"countPerStatus":{},"posts":[],"tags":[]},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"en","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"isCommercial":false},"title":"My Page Title · "}
48+
{"contextID":"CONTEXT_ID","description":"My Page Description","page":"Test.page","props":{"countPerStatus":{},"posts":[],"tags":[]},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"en","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"hasCommercialFeatures":false},"title":"My Page Title · "}
4949

5050
</script>
5151

app/pkg/web/testdata/tenant.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h2 class="text-display2">Please enable JavaScript</h2>
4545

4646
<script id="server-data" type="application/json">
4747

48-
{"contextID":"CONTEXT_ID","page":"","props":{},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"Game of Thrones","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"isCommercial":false},"title":"Game of Thrones"}
48+
{"contextID":"CONTEXT_ID","page":"","props":{},"sessionID":"","settings":{"allowAllowedSchemes":true,"assetsURL":"https://demo.test.fider.io:3000","baseURL":"https://demo.test.fider.io:3000","domain":".test.fider.io","environment":"test","googleAnalytics":"","hasLegal":true,"isBillingEnabled":false,"locale":"en","localeDirection":"ltr","mode":"multi","oauth":[],"postWithTags":true},"tenant":{"id":0,"name":"Game of Thrones","subdomain":"","invitation":"","welcomeMessage":"","welcomeHeader":"","cname":"","status":0,"locale":"","isPrivate":false,"logoBlobKey":"","allowedSchemes":"","isEmailAuthAllowed":false,"isFeedEnabled":false,"preventIndexing":false,"isModerationEnabled":false,"hasCommercialFeatures":false},"title":"Game of Thrones"}
4949

5050
</script>
5151

0 commit comments

Comments
 (0)