forked from baron/baron-sso
비밀번호 재설정 중복 완료 요청 문제 수정
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -254,6 +255,65 @@ func TestCompletePasswordReset_InvalidTokenRejectedEvenWhenLoginIDExists(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletePasswordReset_DuplicateTokenSubmitIsIdempotent(t *testing.T) {
|
||||
const resetToken = "dup-token"
|
||||
const loginID = "user@example.com"
|
||||
const newPassword = "StrongPass1!"
|
||||
|
||||
redis := &testRedisRepo{
|
||||
values: map[string]string{
|
||||
prefixPwdResetToken + resetToken: loginID,
|
||||
},
|
||||
}
|
||||
idp := &mockIdpProvider{
|
||||
userExists: true,
|
||||
err: nil,
|
||||
}
|
||||
h := &AuthHandler{
|
||||
RedisService: redis,
|
||||
IdpProvider: idp,
|
||||
}
|
||||
app := newResetFlowTestApp(h)
|
||||
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"newPassword": newPassword,
|
||||
})
|
||||
url := fmt.Sprintf(
|
||||
"/api/v1/auth/password/reset/complete?token=%s",
|
||||
resetToken,
|
||||
)
|
||||
|
||||
firstReq := httptest.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||||
firstReq.Header.Set("Content-Type", "application/json")
|
||||
firstResp, err := app.Test(firstReq)
|
||||
if err != nil {
|
||||
t.Fatalf("first request failed: %v", err)
|
||||
}
|
||||
defer firstResp.Body.Close()
|
||||
|
||||
if firstResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected first response to be 200, got %d", firstResp.StatusCode)
|
||||
}
|
||||
if idp.updateCallCount != 1 {
|
||||
t.Fatalf("expected first request to update password once, got %d", idp.updateCallCount)
|
||||
}
|
||||
|
||||
secondReq := httptest.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||||
secondReq.Header.Set("Content-Type", "application/json")
|
||||
secondResp, err := app.Test(secondReq)
|
||||
if err != nil {
|
||||
t.Fatalf("second request failed: %v", err)
|
||||
}
|
||||
defer secondResp.Body.Close()
|
||||
|
||||
if secondResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected duplicate response to be 200, got %d", secondResp.StatusCode)
|
||||
}
|
||||
if idp.updateCallCount != 1 {
|
||||
t.Fatalf("expected duplicate request not to update password again, got %d", idp.updateCallCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessPasswordResetToken_EncodesLoginIDInRedirect(t *testing.T) {
|
||||
const token = "tok-enc"
|
||||
const loginID = "user+alias@example.com"
|
||||
@@ -295,6 +355,102 @@ func TestProcessPasswordResetToken_EncodesLoginIDInRedirect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetVerifyAlias_AcceptsShortVePath(t *testing.T) {
|
||||
const token = "tok-ve"
|
||||
const loginID = "user@example.com"
|
||||
|
||||
redis := &testRedisRepo{
|
||||
values: map[string]string{
|
||||
prefixPwdResetToken + token: loginID,
|
||||
},
|
||||
}
|
||||
h := &AuthHandler{
|
||||
RedisService: redis,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/api/v1/auth/password/reset/ve", h.VerifyPasswordResetPage)
|
||||
app.Post("/api/v1/auth/password/reset/ve", h.ProcessPasswordResetToken)
|
||||
|
||||
getReq := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/api/v1/auth/password/reset/ve?token="+token,
|
||||
nil,
|
||||
)
|
||||
getResp, err := app.Test(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("get request failed: %v", err)
|
||||
}
|
||||
defer getResp.Body.Close()
|
||||
|
||||
if getResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected alias GET to return 200, got %d", getResp.StatusCode)
|
||||
}
|
||||
|
||||
postReq := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/api/v1/auth/password/reset/ve?token="+token,
|
||||
nil,
|
||||
)
|
||||
postResp, err := app.Test(postReq)
|
||||
if err != nil {
|
||||
t.Fatalf("post request failed: %v", err)
|
||||
}
|
||||
defer postResp.Body.Close()
|
||||
|
||||
if postResp.StatusCode != http.StatusFound {
|
||||
t.Fatalf("expected alias POST to return 302, got %d", postResp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetVerifyPathToken_AcceptsShortVPath(t *testing.T) {
|
||||
const token = "tok-path"
|
||||
const loginID = "user@example.com"
|
||||
|
||||
redis := &testRedisRepo{
|
||||
values: map[string]string{
|
||||
prefixPwdResetToken + token: loginID,
|
||||
},
|
||||
}
|
||||
h := &AuthHandler{
|
||||
RedisService: redis,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Get("/api/v1/auth/password/reset/v/:token", h.VerifyPasswordResetPage)
|
||||
app.Post("/api/v1/auth/password/reset/v/:token", h.ProcessPasswordResetToken)
|
||||
|
||||
getReq := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/api/v1/auth/password/reset/v/"+token,
|
||||
nil,
|
||||
)
|
||||
getResp, err := app.Test(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("get request failed: %v", err)
|
||||
}
|
||||
defer getResp.Body.Close()
|
||||
|
||||
if getResp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected path-token GET to return 200, got %d", getResp.StatusCode)
|
||||
}
|
||||
|
||||
postReq := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/api/v1/auth/password/reset/v/"+token,
|
||||
nil,
|
||||
)
|
||||
postResp, err := app.Test(postReq)
|
||||
if err != nil {
|
||||
t.Fatalf("post request failed: %v", err)
|
||||
}
|
||||
defer postResp.Body.Close()
|
||||
|
||||
if postResp.StatusCode != http.StatusFound {
|
||||
t.Fatalf("expected path-token POST to return 302, got %d", postResp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetInit_LegacyErrorResponseHasCodeViaMiddleware(t *testing.T) {
|
||||
h := &AuthHandler{}
|
||||
app := newResetInitAppWithErrorCodeEnricher(h)
|
||||
@@ -326,3 +482,40 @@ func TestPasswordResetInit_LegacyErrorResponseHasCodeViaMiddleware(t *testing.T)
|
||||
t.Fatalf("expected code=bad_request, got %v", got["code"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitiatePasswordReset_SmsContainsVerifyLink(t *testing.T) {
|
||||
t.Setenv("USERFRONT_URL", "https://sss.hmac.kr")
|
||||
|
||||
redis := &testRedisRepo{values: map[string]string{}}
|
||||
smsSvc := &mockSmsService{}
|
||||
h := &AuthHandler{
|
||||
RedisService: redis,
|
||||
IdpProvider: &mockIdpProvider{},
|
||||
SmsService: smsSvc,
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
app.Post("/api/v1/auth/password/reset/init", h.InitiatePasswordReset)
|
||||
|
||||
body, _ := json.Marshal(map[string]string{
|
||||
"loginId": "01012345678",
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/password/reset/init", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if !strings.Contains(smsSvc.lastContent, "/api/v1/auth/password/reset/v/") {
|
||||
t.Fatalf("expected SMS to contain short path verify link, got %q", smsSvc.lastContent)
|
||||
}
|
||||
if strings.Contains(smsSvc.lastContent, "/reset-password?token=") {
|
||||
t.Fatalf("expected direct reset-password link to be removed, got %q", smsSvc.lastContent)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user