Accounts — services¶
Responsibilities¶
The accounts services contain the business logic for workflows that should not live in views or serializers.
They are responsible for:
- authentication and session lifecycle operations
- password change and password reset issuance
- invite lifecycle operations
- role-based invite permission checks
- account creation during invite acceptance
- device/session mutation tied to authentication
They are not responsible for:
- HTTP request/response handling
- serializer field validation
- URL routing
- admin configuration
- persistence-only model declarations
In this app, services are the place where multi-step domain actions live.
Main services¶
services.auth¶
Business logic for authentication, session management, and password workflows.
Responsibilities¶
- resolve login identifier to the correct user account
- authenticate and create device-backed sessions
- revoke one or all refresh-token sessions
- list user devices
- change password
- issue password reset emails
- reset password after token validation
Main functions¶
resolve_login_username(identifier)¶
Resolves a login identifier to the username expected by Django authentication.
- Inputs
identifier- Behavior
- accepts username directly
- if the identifier looks like an email address, resolves the matching user by email
- Returns
- username string or
None
This allows the backend to support both username-based and email-based login without changing the underlying auth backend contract.
login_user(...)¶
Authenticates a user and registers or updates their device/session.
- Inputs
requestidentifierpassworddevice_idplatformdevice_nameapp_versionpush_token- Behavior
- resolves login identifier
- authenticates the user
- upserts the
Device - creates a refresh token
- creates a
DeviceSession - Returns
- dictionary with:
userdeviceaccessrefresh
- Raises
InvalidCredentialsError
This is the core login workflow for mobile and web clients.
logout_user(user, refresh_token)¶
Revokes a single refresh-token session.
- Inputs
userrefresh_token- Behavior
- parses the refresh token
- extracts JTI
- blacklists the token if present in outstanding tokens
- marks matching
DeviceSessionas revoked - Raises
InvalidRefreshTokenError
This is the single-session logout workflow.
logout_all_user_sessions(user)¶
Revokes all active sessions for a user.
- Inputs
user- Behavior
- finds all active
DeviceSessionrows for the user - blacklists each refresh token JTI
- marks all sessions revoked
- Returns
- count of revoked sessions
This is the “log out everywhere” workflow.
list_user_devices(user)¶
Returns registered devices for a user.
- Inputs
user- Behavior
- queries devices for the user
- prefetches sessions
- orders by recent activity
- Returns
- queryset of
Device
This is used by device/session management endpoints.
revoke_user_session(user, session_id)¶
Revokes a single owned session.
- Inputs
usersession_id- Behavior
- loads the session only if it belongs to the user
- blacklists its refresh JTI
- marks the session revoked
- Returns
- revoked
DeviceSession - Raises
SessionNotFoundError
This is used for targeted session revocation from device/session screens.
change_user_password(user, new_password)¶
Changes a logged-in user’s password.
- Inputs
usernew_password- Behavior
- sets the new password
- saves the user
- Returns
- nothing
This function assumes validation has already been performed by the serializer.
issue_password_reset(email)¶
Issues a password reset email if the account exists.
- Inputs
email- Behavior
- normalizes the email
- looks up active user
- generates uid/token pair
- queues password reset email task
- Returns
Trueif a matching user was foundFalseotherwise
The API layer still returns a generic success response to avoid account enumeration.
reset_user_password(user, new_password)¶
Sets a new password after the reset token has already been validated.
- Inputs
usernew_password- Behavior
- updates stored password hash
- Returns
- nothing
Exceptions¶
AuthError¶
Base auth service exception.
InvalidCredentialsError¶
Raised when identifier resolution or authentication fails.
InvalidRefreshTokenError¶
Raised when logout receives an invalid refresh token.
SessionNotFoundError¶
Raised when a requested session does not exist for the given user.
services.invite¶
Business logic for invite lifecycle management.
Responsibilities¶
- determine whether a user may invite into an organization
- create invites with duplicate protection
- resend invites
- cancel invites
- accept invites
- create user accounts for invite acceptance when needed
- assign org/customer membership on acceptance
- expose filtered invite listings
Main functions¶
can_invite_to_org(user, org)¶
Determines whether a user may manage invites for an organization.
- Inputs
userorg- Behavior
- allows superusers
- otherwise requires active org membership with admin/owner role
- Returns
- boolean
This is the role-based access gate for invite administration.
generate_unique_username(base_username)¶
Builds a unique username candidate for invite-created accounts.
- Inputs
base_username- Behavior
- starts from a normalized base
- appends an incrementing suffix while the username exists
- Returns
- unique username string
This prevents username collisions when a new account is created from an invite email.
get_active_duplicate_invite(invite_type, email, org)¶
Checks for an already-active pending invite.
- Inputs
invite_typeemailorg- Behavior
- matches same invite type, email, and org
- only returns invites that are:
- not accepted
- not cancelled
- not expired
- Returns
- matching
InviteorNone
This supports duplicate active invite protection.
list_invites_for_org(org, status_filter=None)¶
Lists invites for one organization.
- Inputs
orgstatus_filter- Behavior
- returns invites ordered by newest first
- can filter by:
pendingacceptedexpiredcancelled
- Returns
- queryset or filtered list of
Invite - Raises
ValueErrorfor unsupported status filter
This is used by invite management screens.
create_invite(...)¶
Creates a new invite and optionally sends email.
- Inputs
created_byinvite_typeemailorgroleexpires_in_dayssend_email- Behavior
- normalizes email/role
- checks for duplicate active invite
- creates the invite
- queues email if enabled
- Returns
- new
Invite - Raises
InviteAlreadyExistsError
This is the primary onboarding entry point.
resend_invite(invite, extend_if_expired=True, extension_days=14, send_email=True)¶
Resends an invite.
- Inputs
inviteextend_if_expiredextension_dayssend_email- Behavior
- rejects cancelled invites
- rejects accepted invites
- optionally extends expired invites
- queues invite email
- Returns
- updated
Invite - Raises
InviteStateError
This is used when admins need to re-notify a user without creating a new invite.
cancel_invite(invite, cancelled_by)¶
Cancels a pending invite.
- Inputs
invitecancelled_by- Behavior
- no-ops for already-cancelled invite
- rejects accepted invite
- stamps
cancelled_atandcancelled_by - Returns
- updated
Invite - Raises
InviteStateError
This makes pending invites unusable without deleting historical records.
accept_invite(invite, actor_user, password=None)¶
Accepts an invite and ensures the correct membership is created.
- Inputs
inviteactor_userpassword- Behavior
- rejects invalid invite states
- if authenticated:
- requires the authenticated user email to match the invite email
- if anonymous:
- reuses existing account by email, or
- creates a new user when password is supplied
- creates or updates:
OrgMembershipfor internal invitesCustomerMembershipfor customer invites
- marks invite accepted
- Returns
- tuple of
(user, invite) - Raises
InviteStateErrorInvitePermissionErrorInviteValidationError
This is the core invite-consumption workflow.
Exceptions¶
InviteError¶
Base invite service exception.
InviteAlreadyExistsError¶
Raised when an active duplicate invite already exists.
Carries:
- invite — the existing conflicting invite
InviteStateError¶
Raised when an invite is in the wrong lifecycle state for the requested action.
Examples: - resending a cancelled invite - accepting an expired invite - cancelling an accepted invite
InvitePermissionError¶
Raised when the acting user is not allowed to accept the invite.
Most commonly: - authenticated user email does not match invite email
InviteValidationError¶
Raised when required input for the invite workflow is missing or invalid.
Most commonly: - anonymous acceptance requires account creation, but no password was supplied
Service relationship overview¶
flowchart TD
AuthService["services.auth"]
InviteService["services.invite"]
User["User"]
Device["Device"]
DeviceSession["DeviceSession"]
Invite["Invite"]
OrgMembership["OrgMembership"]
CustomerMembership["CustomerMembership"]
EmailTasks["Email tasks"]
AuthService --> User
AuthService --> Device
AuthService --> DeviceSession
AuthService --> EmailTasks
InviteService --> Invite
InviteService --> User
InviteService --> OrgMembership
InviteService --> CustomerMembership
InviteService --> EmailTasks