feat: account connect
This commit is contained in:
@@ -44,6 +44,31 @@ func (r *OAuthAccountRepo) FindByProvider(ctx context.Context, provider, provide
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// ListProvidersForUser returns the distinct provider names linked to this
|
||||
// user, ordered alphabetically. Used by the settings UI to mark each
|
||||
// social-connect row as "Connected" vs "Connect".
|
||||
func (r *OAuthAccountRepo) ListProvidersForUser(ctx context.Context, userID string) ([]string, error) {
|
||||
const q = `
|
||||
SELECT DISTINCT provider
|
||||
FROM oauth_accounts
|
||||
WHERE user_id = $1
|
||||
ORDER BY provider`
|
||||
rows, err := r.pg.Pool.Query(ctx, q, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list oauth providers: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []string{}
|
||||
for rows.Next() {
|
||||
var p string
|
||||
if err := rows.Scan(&p); err != nil {
|
||||
return nil, fmt.Errorf("scan oauth provider: %w", err)
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// Link inserts a new oauth_accounts row tying a provider identity to an
|
||||
// existing user. Idempotent: a second call for the same (provider, sub)
|
||||
// returns the existing row instead of erroring on the unique constraint.
|
||||
|
||||
@@ -380,6 +380,32 @@ func (r *AuthTokenRepo) FindValid(ctx context.Context, tokenHash, purpose string
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// FindValidForUser returns a non-used, non-expired token by user + hash +
|
||||
// purpose. Used by the OTP login flow where the keyspace is small (6
|
||||
// digits) — scoping by user_id prevents a global hash-collision lookup.
|
||||
func (r *AuthTokenRepo) FindValidForUser(ctx context.Context, userID, tokenHash, purpose string) (*model.AuthToken, error) {
|
||||
const q = `
|
||||
SELECT id, user_id, purpose, token_hash, expires_at, used_at, created_at
|
||||
FROM auth_tokens
|
||||
WHERE user_id = $1
|
||||
AND token_hash = $2
|
||||
AND purpose = $3
|
||||
AND used_at IS NULL
|
||||
AND expires_at > now()
|
||||
LIMIT 1`
|
||||
|
||||
t := &model.AuthToken{}
|
||||
err := r.pg.Pool.QueryRow(ctx, q, userID, tokenHash, purpose).
|
||||
Scan(&t.ID, &t.UserID, &t.Purpose, &t.TokenHash, &t.ExpiresAt, &t.UsedAt, &t.CreatedAt)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("find token: %w", err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Consume marks a token as used. After this call the token can never be
|
||||
// honoured again, even if it has not yet expired.
|
||||
func (r *AuthTokenRepo) Consume(ctx context.Context, id string) error {
|
||||
|
||||
Reference in New Issue
Block a user