Skip to content
Open

Main #32

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"Bash(/Users/rexraphael/Work/xraph/forgery/authsome/store/bun/store.go:*)",
"Bash(/Users/rexraphael/Work/xraph/forgery/authsome/middleware/rbac.go:*)",
"Bash(/Users/rexraphael/Work/xraph/forgery/authsome/rbac/store_memory_test.go:*)",
"Bash(done)"
"Bash(done)",
"Bash(make f *)"
]
}
}
4 changes: 2 additions & 2 deletions dashboard/contributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ func (c *Contributor) renderUserDetail(ctx context.Context, appID id.AppID, para
actionError = "Failed to delete user: " + delErr.Error()
} else {
// Redirect to users list after deletion.
users, err := fetchUsers(ctx, c.engine, appID, "", 25)
if users == nil || err != nil {
users, fetchErr := fetchUsers(ctx, c.engine, appID, "", 25)
if users == nil || fetchErr != nil {
users = &user.List{}
}
return pages.UsersPage(users, "", "../users"), nil
Expand Down
4 changes: 2 additions & 2 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -712,11 +712,11 @@ func (e *Engine) RebindLedgerOnPlugins() {
if e.ledgerEng == nil || e.plugins == nil {
return
}
store := e.ledgerEng.Store()
ledgerStore := e.ledgerEng.Store()
for _, p := range e.plugins.Plugins() {
if rb, ok := p.(ledgerRebindable); ok {
rb.SetLedger(e.ledgerEng)
rb.SetLedgerStore(store)
rb.SetLedgerStore(ledgerStore)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion extension/dashboard_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (p *proxyContributor) fetchFragment(ctx context.Context, target, basePath,
target = appendQuery(target, basePathQueryParam, basePath)
target = appendQuery(target, pageBaseQueryParam, pageBase)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, http.NoBody)
if err != nil {
return nil, fmt.Errorf("authsome proxy: build request: %w", err)
}
Expand Down
56 changes: 55 additions & 1 deletion plugins/social/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,69 @@ func (p *githubProvider) FetchUser(ctx context.Context, token *oauth2.Token) (*P
return nil, fmt.Errorf("social: github: decode user: %w", err)
}

// GitHub's /user endpoint only returns the user's *public* email. Accounts
// that keep their email private (the default) return an empty string here.
// Fall back to /user/emails to fetch the primary verified email. This
// requires the "user:email" scope (already requested by default above).
email := info.Email
if email == "" {
if primary, emailErr := p.fetchPrimaryEmail(ctx, client); emailErr == nil {
email = primary
}
}

name := info.Name
if name == "" {
name = info.Login
}

return &ProviderUser{
ProviderUserID: fmt.Sprintf("%d", info.ID),
Email: info.Email,
Email: email,
FirstName: name,
AvatarURL: info.AvatarURL,
}, nil
}

// fetchPrimaryEmail calls GitHub's /user/emails endpoint and returns the
// user's primary verified email. Requires the "user:email" OAuth scope.
// Returns an empty string (no error) if no verified primary is available.
func (p *githubProvider) fetchPrimaryEmail(ctx context.Context, client *http.Client) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/user/emails", http.NoBody)
if err != nil {
return "", fmt.Errorf("social: github: create emails request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("social: github: fetch emails: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body) //nolint:errcheck // best-effort read
return "", fmt.Errorf("social: github: fetch emails: status %d: %s", resp.StatusCode, body)
}

var emails []struct {
Email string `json:"email"`
Primary bool `json:"primary"`
Verified bool `json:"verified"`
}
if err := json.NewDecoder(resp.Body).Decode(&emails); err != nil {
return "", fmt.Errorf("social: github: decode emails: %w", err)
}

// Prefer primary + verified.
for _, e := range emails {
if e.Primary && e.Verified {
return e.Email, nil
}
}
// Fall back to any verified email.
for _, e := range emails {
if e.Verified {
return e.Email, nil
}
}
return "", nil
}
2 changes: 1 addition & 1 deletion service.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func (e *Engine) SignIn(ctx context.Context, req *account.SignInRequest) (*user.
if req.AppID.Prefix() != "" {
resolveOpts.AppID = req.AppID.String()
}
if dynVal, err := settings.Get(ctx, mgr, SettingRequireEmailVerification, resolveOpts); err == nil && dynVal {
if dynVal, dynErr := settings.Get(ctx, mgr, SettingRequireEmailVerification, resolveOpts); dynErr == nil && dynVal {
requireVerif = true
}
}
Expand Down
8 changes: 4 additions & 4 deletions switchorg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestSwitchActiveOrg_clearsActiveOrg(t *testing.T) {
eng, store := newTestEngine(t, authsome.WithPlugin(&fakeOrgPlugin{}))
ctx := context.Background()

sess := newSeedSession(t, store, ctx, id.NewOrgID())
sess := newSeedSession(ctx, t, store, id.NewOrgID())

// Empty newOrgID is allowed and clears the active org.
updated, err := eng.SwitchActiveOrg(ctx, sess.ID, id.OrgID{})
Expand Down Expand Up @@ -93,7 +93,7 @@ func TestSwitchActiveOrg_pluginMissing_returnsError(t *testing.T) {
// must error rather than panic on the nil plugin.
eng, store := newTestEngine(t)
ctx := context.Background()
sess := newSeedSession(t, store, ctx, id.OrgID{})
sess := newSeedSession(ctx, t, store, id.OrgID{})

_, err := eng.SwitchActiveOrg(ctx, sess.ID, id.NewOrgID())
require.Error(t, err)
Expand All @@ -103,9 +103,9 @@ func TestSwitchActiveOrg_pluginMissing_returnsError(t *testing.T) {
// newSeedSession persists a fresh session for a synthetic user and
// returns it. The session has no OrgID by default unless the caller
// passes one.
func newSeedSession(t *testing.T, store interface {
func newSeedSession(ctx context.Context, t *testing.T, store interface {
CreateSession(ctx context.Context, s *session.Session) error
}, ctx context.Context, orgID id.OrgID) *session.Session {
}, orgID id.OrgID) *session.Session {
t.Helper()
sess := &session.Session{
ID: id.NewSessionID(),
Expand Down
Loading