Improve youth mode action token flow
This commit is contained in:
+15
-3
@@ -3021,7 +3021,7 @@ func serveIMYouthModeEnable(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "malformed request", nil)
|
||||
return
|
||||
}
|
||||
if _, err := store.Security.ConsumeActionToken(principal.TenantID, principal.Uid, strings.TrimSpace(body.ActionToken), "set_independent_password", "youth_mode", time.Now().UTC().Round(time.Millisecond)); err != nil {
|
||||
if _, err := store.Security.ConsumeActionToken(principal.TenantID, principal.Uid, strings.TrimSpace(body.ActionToken), "enable_youth_mode", "youth_mode", time.Now().UTC().Round(time.Millisecond)); err != nil {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30042, imReasonActionToken, "action token invalid", nil)
|
||||
return
|
||||
}
|
||||
@@ -6081,7 +6081,19 @@ func serveIMIndependentPassword(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||
return
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{"status": "ok", "purpose": purpose})
|
||||
enableToken, enableExpiresAt, err := issueIMActionToken(principal.TenantID, principal.Uid, "enable_youth_mode", purpose)
|
||||
if err != nil {
|
||||
writeIMError(wrt, req, http.StatusInternalServerError, 90000, imReasonInternal, "internal error", nil)
|
||||
return
|
||||
}
|
||||
writeIMSuccess(wrt, req, map[string]any{
|
||||
"status": "ok",
|
||||
"password_set": true,
|
||||
"purpose": purpose,
|
||||
"enable_action_token": enableToken,
|
||||
"enable_action_expires_at": enableExpiresAt.Format(time.RFC3339Nano),
|
||||
"next_action": "enable_youth_mode",
|
||||
})
|
||||
}
|
||||
|
||||
func serveIMIndependentPasswordVerify(wrt http.ResponseWriter, req *http.Request) {
|
||||
@@ -6338,7 +6350,7 @@ func issueIMActionToken(tenantID types.TenantID, uid types.Uid, action, target s
|
||||
|
||||
func defaultIMActionTarget(action, fallback string) string {
|
||||
switch action {
|
||||
case "set_independent_password", "close_youth_mode", "reset_independent_password":
|
||||
case "set_independent_password", "enable_youth_mode", "close_youth_mode", "reset_independent_password":
|
||||
return "youth_mode"
|
||||
default:
|
||||
return fallback
|
||||
|
||||
+12
-2
@@ -2132,6 +2132,13 @@ func TestServeIMIndependentPasswordSetAndVerify(t *testing.T) {
|
||||
bcrypt.CompareHashAndPassword(security.independentPassword.PasswordHash, []byte("2794")) != nil {
|
||||
t.Fatalf("password was not stored as a valid hash: %#v", security.independentPassword)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["password_set"] != true || data["next_action"] != "enable_youth_mode" || data["enable_action_token"] == "" {
|
||||
t.Fatalf("unexpected set response: %#v", data)
|
||||
}
|
||||
if security.actionToken == nil || security.actionToken.Action != "enable_youth_mode" || security.actionToken.Target != "youth_mode" {
|
||||
t.Fatalf("unexpected enable token after set: %#v", security.actionToken)
|
||||
}
|
||||
|
||||
expectIMBearer(t, ctrl, "token-verify", 7, 42)
|
||||
req = httptest.NewRequest(http.MethodPost, "/v1/im/settings/independent-password/verify",
|
||||
@@ -2144,7 +2151,7 @@ func TestServeIMIndependentPasswordSetAndVerify(t *testing.T) {
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("verify status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
data = decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["action"] != "close_youth_mode" || data["action_token"] == "" {
|
||||
t.Fatalf("unexpected verify response: %#v", data)
|
||||
}
|
||||
@@ -3156,7 +3163,7 @@ func TestServeIMYouthModeEnableDisable(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
oldSecurity := store.Security
|
||||
security := &testSecurityStore{
|
||||
actionToken: &types.ActionToken{Action: "set_independent_password", Target: "youth_mode"},
|
||||
actionToken: &types.ActionToken{Action: "enable_youth_mode", Target: "youth_mode"},
|
||||
independentPassword: &types.IndependentPassword{PasswordHash: []byte("hash")},
|
||||
}
|
||||
store.Security = security
|
||||
@@ -3172,6 +3179,9 @@ func TestServeIMYouthModeEnableDisable(t *testing.T) {
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("enable status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||
}
|
||||
if security.consumedAction != "enable_youth_mode" || security.consumedTarget != "youth_mode" {
|
||||
t.Fatalf("unexpected enable token consumption: action=%q target=%q", security.consumedAction, security.consumedTarget)
|
||||
}
|
||||
data := decodeIMHTTPResponse(t, rec).Data.(map[string]any)
|
||||
if data["enabled"] != true || data["policy_version"].(float64) != 2 {
|
||||
t.Fatalf("unexpected youth enable payload: %#v", data)
|
||||
|
||||
@@ -223,6 +223,8 @@ type testSecurityStore struct {
|
||||
realName *types.RealNameVerification
|
||||
realNameAppeal *types.RealNameAppeal
|
||||
actionToken *types.ActionToken
|
||||
consumedAction string
|
||||
consumedTarget string
|
||||
independentPassword *types.IndependentPassword
|
||||
youthMode *types.YouthModeSettings
|
||||
attempts int
|
||||
@@ -294,6 +296,8 @@ func (s *testSecurityStore) CreateActionToken(_ types.TenantID, token *types.Act
|
||||
}
|
||||
|
||||
func (s *testSecurityStore) ConsumeActionToken(_ types.TenantID, _ types.Uid, _ string, action string, target string, _ time.Time) (*types.ActionToken, error) {
|
||||
s.consumedAction = action
|
||||
s.consumedTarget = target
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user