Repository navigation
feat(auth): 이메일 인증 후 회원 생성 + 회장 회원 추가 - #448
Merged
Merged
Conversation
Separate pending signup data from real members and add a president-only direct member creation path. Constraint: Preserve cached signup client request flow Rejected: Create unverified members then expire them | still occupies the student ID and leaks pending accounts into member surfaces Directive: Never store signup passwords or verification codes in plaintext Confidence: high Scope-risk: broad Not-tested: Implementation follows after design approval
…oning Plan the pending signup lifecycle, compatibility contract, admin creation, verification and production cleanup as independently testable tasks. Constraint: General signup must not create members before verification Directive: Execute each task test-first and keep production cleanup after V92 deployment Confidence: high Scope-risk: broad Tested: Plan self-review for spec coverage, placeholders and interface consistency
Pending signup state now has its own persistence model, and roster signup validation can produce an immutable prepared result without claiming a graduate roster row until confirmation. This keeps unverified user-supplied data out of member identity tables while preserving the current AuthService lifecycle for later tasks. Constraint: Task 1 only; no frontend, AuthController/AuthService lifecycle, admin creation, OpenAPI, production, or main changes. Rejected: Reusing validateAndClaimGraduateSignup for preparation | it mutates the roster before email ownership is proven. Confidence: high Scope-risk: narrow Directive: Do not wire pending signup into AuthService until the signup lifecycle task owns email send, resend, confirm, and rollback semantics. Tested: ./gradlew test --tests '*PendingSignupPersistenceTest' --tests '*EligibleMemberServiceTest' --tests '*LatestMigrationsSmokeTest' --tests '*MigrationVersionTest' Not-tested: Real PostgreSQL PostgresMigrationExecutionTest; COMS_PG_TEST_URL was not set locally.
The prepared-signup claim path now declares its own write transaction, and lock-taking repository methods are explicit ForUpdate queries instead of locking ordinary finder methods. This keeps non-locking reads safe while preserving pessimistic locking where confirmation mutates roster identity. Constraint: Fix round 1 only; no Task 2, AuthService lifecycle, frontend, admin, OpenAPI, production, or main changes. Rejected: Leaving lock annotations on ordinary finders | non-locking reads should not unexpectedly request pessimistic locks. Confidence: high Scope-risk: narrow Tested: ./gradlew test --tests '*EligibleMemberSignupLockingTest' --tests '*PendingSignupPersistenceTest' Tested: ./gradlew test --tests '*EligibleMemberServiceTest' --tests '*EligibleMemberSignupLockingTest' --tests '*PendingSignupPersistenceTest' --tests '*LatestMigrationsSmokeTest' --tests '*MigrationVersionTest' Not-tested: Real PostgreSQL PostgresMigrationExecutionTest; COMS_PG_TEST_URL was not set locally.
Resend now commits the pending-code replacement before SMTP and uses a REQUIRES_NEW exact-hash cleanup that only expires the failed version, leaving newer pending rows intact. Constraint: Task 2 fix scope only; preserve existing API sequence and AuthResponse fields Rejected: Keep SMTP inside @transactional | rollback and cleanup semantics could touch uncommitted or stale pending state Confidence: high Scope-risk: narrow Tested: ./gradlew test --tests '*PendingSignupServiceTest' --tests '*AuthControllerCookieTest' --tests '*AuthServiceTest' --tests '*LoginFailureRetentionJobTest' --tests '*AuthServiceLoginThrottleTest' Tested: ./gradlew test
Direct admin member creation must not mint graduate-year numeric accounts or accidentally admit future enum roles. The service now delegates the current-student admission-year decision to EligibleMemberService, which already owns clock-based graduate classification, and AdminService uses an explicit role allowlist for creatable non-president roles. Constraint: Task 3 fix round limited changes to Task 3 backend files and report Rejected: Duplicate admission-year cutoff logic in AdminService | would drift from EligibleMemberService clock behavior Confidence: high Scope-risk: narrow Tested: ./gradlew test --tests '*AdminServiceTest' --tests '*OperationsSecurityIntegrationTest' Tested: ./gradlew test
Regenerated the OpenAPI document from the dev/H2 backend on the Task 5 isolated port so downstream clients see the settled pending-signup and admin-member contract surface. Constraint: Task 5 local scope permits only backend/openapi.json changes and forbids push, PR, merge, deploy, production writes, or member deletion Constraint: OpenAPI was captured from dev/H2 on server.port=18083 and the listener PID was stopped after capture Confidence: high Scope-risk: narrow Tested: ./gradlew test Tested: ./gradlew test --rerun-tasks Tested: npm test Tested: npm run build Tested: npm run smoke Tested: npm run test:e2e:backend Not-tested: PR, merge, deployment, production health, production anonymous 403, and unverified-member cleanup are explicitly out of local-only scope
Expired pending rows with a candidate email are removed inside the signup start transaction before duplicate validation, while active conflicts and the lower(email) unique index remain intact. The admin create form now also has handler-level duplicate-submit protection and alert semantics for create errors. Constraint: Keep the lower(email) pending signup unique index and do not alter spec, plan, push, PR, or production state. Rejected: Waiting for nightly cleanup to free expired pending email conflicts | leaves a valid signup blocked by stale data. Rejected: Rely only on disabled button state for admin create submit | React state can race two rapid submit events. Confidence: high Scope-risk: narrow Tested: cd backend && ./gradlew test --tests '*PendingSignupServiceTest' Tested: cd backend && ./gradlew test --tests '*PendingSignupServiceTest' --tests '*AuthControllerCookieTest' --tests '*AuthServiceTest' --tests '*LoginFailureRetentionJobTest' --tests '*AuthServiceLoginThrottleTest' Tested: node --import tsx tests/preverifiedSignup.test.mjs && node --import tsx tests/adminAccessibility.test.mjs && npm run smoke -- --grep 'signup|president adds' Tested: cd backend && ./gradlew test Tested: npm test Tested: npm run build && npm run smoke Not-tested: PostgreSQL migration execution locally because COMS_PG_TEST_URL is not set; no push, PR, deployment, or production cleanup performed.
Hold bounded email serialization through transaction completion and convert only the named pending-email unique collision into the public conflict contract. Constraint: The email monitor must span TransactionTemplate commit and remain bounded under attacker-controlled input. Constraint: Keep uq_pending_signups_email_ci as the cross-instance authority. Rejected: Per-email ConcurrentHashMap locks | unbounded growth from arbitrary candidate emails. Rejected: Translate every DataIntegrityViolationException | would mask unrelated persistence defects. Confidence: high Scope-risk: narrow Directive: Keep email locking outside the complete transactionTemplate.execute call and scope conflict translation to uq_pending_signups_email_ci. Tested: PendingSignupServiceTest 14 tests, 0 failures; full backend 497 tests, 0 failures, 0 errors, 1 PostgreSQL-gated skip. Not-tested: PostgreSQL migration execution because COMS_PG_TEST_URL is not set; no frontend, push, PR, or production actions.
Remove the accidentally tracked final-fix report from repository history while preserving the local evidence file under the worktree-local exclusion policy. Constraint: Keep the local report file while removing only its tracked index entry. Constraint: .superpowers/ exclusion remains in local Git metadata and must not be committed. Confidence: high Scope-risk: narrow Tested: Verified one staged deletion, local artifact existence, and git check-ignore resolution. Not-tested: Application tests were not run because this cleanup changes no production or test code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
변경 사항
pending_signups에만 임시 저장하고 이메일 인증 성공 시members생성G연도-ID도 인증 성공 트랜잭션에서만 확정409POST /api/admin/members와 회원 관리회원 추가폼호환성
기존
/signup→/email-verification/confirm-signup순서와 응답 필드를 유지해 캐시된 가입 화면도 동작합니다.검증
npm test, production build 통과배포 후 운영 작업
현재 이메일 미인증 회원을 다시 조회해 승인된 대상만 기존 purge 경로로 삭제하고, V92·서비스 상태·감사로그를 검증합니다.