feat: add last login to user sync#49
Conversation
Derive each user's last login from the team audit log, since Dropbox exposes no last-login field on the member endpoints. GetLastLogin queries /2/team_log/get_events filtered to the member's account_id and the login_success event type. Dropbox returns events oldest-first with no descending-sort option, so we page through all of a member's login_success events and keep the max timestamp (typically one page per member). Requires the events.read scope (Team Auditing). A missing scope or a fetch error degrades gracefully: the user is synced without a last-login trait rather than failing the sync. Users with no login in the audit retention window also sync without the trait.
| lastLogin, llRateLimit, llErr := o.GetLastLogin(ctx, user.Profile.AccountID) | ||
| outAnnotations.WithRateLimiting(llRateLimit) | ||
| if llErr != nil { | ||
| // Don't fail the whole sync if last login can't be fetched (e.g. missing | ||
| // events.read scope or a transient error) — sync the user without it. | ||
| logger.Warn("failed to fetch last login; syncing user without it", | ||
| zap.String("account_id", user.Profile.AccountID), | ||
| zap.Error(llErr), | ||
| ) | ||
| lastLogin = nil | ||
| } |
There was a problem hiding this comment.
🟡 Suggestion: On a tenant missing the events.read scope, GetLastLogin returns a 403 for every member, so this Warn fires once per user (potentially thousands per sync). Consider detecting the scope/permission failure once and suppressing the repeat, or logarithmic sampling with a total_occurrences field, to avoid alert noise. (confidence: medium)
Connector PR Review: feat: add last login to user syncBlocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0 Review SummaryScanned the full PR diff for security and correctness. The change derives each member's last login from the team audit log ( Security IssuesNone found. Correctness IssuesNone found. Suggestions
Prompt for AI agents |
|
we probably want to implement it using usage event. I was just checking if it is a good fit for this case! |
What
Adds each user's last login to the user sync as a
WithLastLoginuser trait.Dropbox exposes no last-login field on the member endpoints (
members/list_v2,members/get_info_v2), so it is derived from the team audit log:POST /2/team_log/get_eventsfiltered to the member'saccount_idand thelogin_successevent type.Why it works this way
GetLastLoginpages through all of a member'slogin_successevents and keeps the max timestamp. In practice a member's logins fit in one page (limit 1000), so this is usually a single API call.events.readscope (Team Auditing). A missing scope or a fetch error logs a warning and syncs the user without the last-login trait rather than failing the whole sync. Users with no login within the audit retention window (plan-dependent) likewise sync without the trait.Changes
pkg/connector/dropbox/payload_models.go— request/response models forget_events(GetEventsBody,GetEventsPayload,EventEntry,EventTypeArg)pkg/connector/dropbox/user_endpoints.go—GetLastLogin, paginatinglogin_successevents and returning the max timestamppkg/connector/dropbox/urls.go— documentedGetEventsURLconst + required scopepkg/connector/users.go— thread*time.TimeintouserResource; fetch last login per member duringListTesting
Verified end-to-end against a live Dropbox Business tenant:
go build,go vet, andgo test ./...pass.golangci-lint runintroduces no new issues.Cost note for reviewers
This is one
get_eventscall per user (occasionally more if a very active user'slogin_successcount exceeds 1000 within the retention window). On a large, active tenant that is meaningful extra API traffic. If preferred, this could be reworked as a single bulk scan of theloginsevent category, joining byaccount_idclient-side — happy to switch if reviewers want that trade-off.