Fix SEC-08: make system token issuance atomic
- Add IssueSystemToken() method in internal/db/accounts.go that wraps revoke-old, track-new, and upsert-system_tokens in a single SQLite transaction - Update handleTokenIssue in internal/server/server.go to use the new atomic method instead of three separate DB calls - Update IssueServiceToken in internal/grpcserver/tokenservice.go with the same fix - Add TestIssueSystemTokenAtomic test covering first issue and rotation Security: token issuance now uses a single transaction to prevent inconsistent state (e.g., old token revoked but new token not tracked) if a crash occurs between operations. Follows the same pattern as RenewToken which was already correctly transactional. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,16 +72,15 @@ func (ts *tokenServiceServer) IssueServiceToken(ctx context.Context, req *mciasv
|
||||
return nil, status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
|
||||
// Revoke existing system token if any.
|
||||
// Atomically revoke existing system token (if any), track the new token,
|
||||
// and update system_tokens — all in a single transaction.
|
||||
// Security: prevents inconsistent state if a crash occurs mid-operation.
|
||||
var oldJTI string
|
||||
existing, err := ts.s.db.GetSystemToken(acct.ID)
|
||||
if err == nil && existing != nil {
|
||||
_ = ts.s.db.RevokeToken(existing.JTI, "rotated")
|
||||
oldJTI = existing.JTI
|
||||
}
|
||||
|
||||
if err := ts.s.db.TrackToken(claims.JTI, acct.ID, claims.IssuedAt, claims.ExpiresAt); err != nil {
|
||||
return nil, status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
if err := ts.s.db.SetSystemToken(acct.ID, claims.JTI, claims.ExpiresAt); err != nil {
|
||||
if err := ts.s.db.IssueSystemToken(oldJTI, claims.JTI, acct.ID, claims.IssuedAt, claims.ExpiresAt); err != nil {
|
||||
return nil, status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user