Files
foundry-core/internal/auth/identity.go
T
codex-bot 5c0ad30a7a
Foundry Go checks / check (push) Failing after 2m2s
Foundry Go checks / check (pull_request) Failing after 2m4s
Add Slack OpenID login with verified identities and callback replay protection
2026-11-05 18:49:41 +08:00

68 lines
2.4 KiB
Go

package auth
import (
"context"
"database/sql"
"errors"
"net/url"
"strings"
)
func (s *service) identityGrant(ctx context.Context, project, provider, subject string, email *string, name, picture, challenge string) (string, error) {
name = strings.TrimSpace(name)
if len(name) > 160 {
name = ""
}
if u, err := url.Parse(picture); err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || len(picture) > 2048 {
picture = ""
}
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return "", err
}
defer tx.Rollback()
// Serialize first registration for the same provider identity.
if _, err = tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock(hashtextextended($1,0))", project+":"+provider+":"+subject); err != nil {
return "", err
}
var account string
err = tx.QueryRowContext(ctx, "SELECT account_id::text FROM account_identities WHERE project=$1 AND provider=$2 AND subject=$3", project, provider, subject).Scan(&account)
if errors.Is(err, sql.ErrNoRows) {
var googleSubject *string
if provider == "google" {
googleSubject = &subject
}
err = tx.QueryRowContext(ctx, `
WITH identity AS(SELECT gen_random_uuid() AS id)
INSERT INTO project_accounts(project,id,google_subject,email,username,display_name,avatar_url)
SELECT $1,id,$2,$3,
left(COALESCE(NULLIF(regexp_replace(lower($4),'[^a-z0-9]','','g'),''),'user'),7)||'_'||replace(id::text,'-',''),
$4,NULLIF($5,'') FROM identity RETURNING id::text
`, project, googleSubject, email, name, picture).Scan(&account)
if err != nil {
return "", err
}
_, err = tx.ExecContext(ctx, "INSERT INTO account_identities(project,provider,subject,account_id) VALUES($1,$2,$3,$4)", project, provider, subject, account)
} else if err == nil {
_, err = tx.ExecContext(ctx, `
UPDATE project_accounts SET email=COALESCE($3,email),
display_name=CASE WHEN display_name='' THEN $4 ELSE display_name END,
avatar_url=COALESCE(avatar_url,NULLIF($5,'')),
username=COALESCE(username,left(COALESCE(NULLIF(regexp_replace(lower($4),'[^a-z0-9]','','g'),''),'user'),7)||'_'||replace(id::text,'-','')),
updated_at=now() WHERE project=$1 AND id=$2
`, project, account, email, name, picture)
}
if err != nil {
return "", err
}
grant := token()
_, err = tx.ExecContext(ctx, "INSERT INTO login_grants VALUES($1,$2,$3,$4,now()+interval '1 minute')", digest(grant), project, account, challenge)
if err != nil {
return "", err
}
if err = tx.Commit(); err != nil {
return "", err
}
return grant, nil
}