Skip to content

Commit 8fb927a

Browse files
committed
Security: bind link codes to initiating user (CSRF fix)
CreateForLinking now requires initiatingUserId parameter, stored in UserId field. This prevents CSRF attacks where an attacker generates a link code and tricks a victim into exchanging it, linking the attacker's GitHub to the victim's account. Addresses adversarial review finding #1 (CRITICAL).
1 parent 5457479 commit 8fb927a

File tree

1 file changed

+12
-4
lines changed

1 file changed

+12
-4
lines changed

backend/src/Taskdeck.Domain/Entities/OAuthAuthCode.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ private set
3333
public Guid UserId { get; private set; }
3434

3535
/// <summary>
36-
/// The pre-serialized JWT token to return on successful exchange (login flow only).
36+
/// Legacy field kept for schema compatibility. No longer populated with real JWTs --
37+
/// tokens are re-issued at exchange time from the stored UserId.
3738
/// </summary>
3839
public string Token { get; private set; } = string.Empty;
3940

@@ -67,6 +68,8 @@ private OAuthAuthCode() : base() { }
6768

6869
/// <summary>
6970
/// Creates an auth code for the login flow (token exchange).
71+
/// Token parameter is accepted for backward compatibility but is no longer stored;
72+
/// JWTs are re-issued at exchange time from the UserId.
7073
/// </summary>
7174
public OAuthAuthCode(string code, Guid userId, string token, DateTimeOffset expiresAt)
7275
: base()
@@ -82,16 +85,21 @@ public OAuthAuthCode(string code, Guid userId, string token, DateTimeOffset expi
8285

8386
Code = code;
8487
UserId = userId;
85-
Token = token;
88+
Token = string.Empty; // Never store actual JWT in DB — re-issue at exchange time
8689
Purpose = "login";
8790
ExpiresAt = expiresAt;
8891
}
8992

9093
/// <summary>
9194
/// Creates an auth code for the account linking flow (provider identity exchange).
95+
/// The initiatingUserId binds this code to the user who started the link flow,
96+
/// preventing CSRF attacks where an attacker's GitHub is linked to a victim's account.
9297
/// </summary>
93-
public static OAuthAuthCode CreateForLinking(string code, string providerData, DateTimeOffset expiresAt)
98+
public static OAuthAuthCode CreateForLinking(string code, Guid initiatingUserId, string providerData, DateTimeOffset expiresAt)
9499
{
100+
if (initiatingUserId == Guid.Empty)
101+
throw new DomainException(ErrorCodes.ValidationError, "Initiating user ID is required for linking");
102+
95103
if (string.IsNullOrWhiteSpace(providerData))
96104
throw new DomainException(ErrorCodes.ValidationError, "Provider data cannot be empty for linking");
97105

@@ -101,7 +109,7 @@ public static OAuthAuthCode CreateForLinking(string code, string providerData, D
101109
var entity = new OAuthAuthCode
102110
{
103111
Code = code,
104-
UserId = Guid.Empty,
112+
UserId = initiatingUserId,
105113
Token = string.Empty,
106114
Purpose = "link",
107115
ProviderData = providerData,

0 commit comments

Comments
 (0)