crossauth_backend.common package

Submodules

crossauth_backend.common.error module

exception crossauth_backend.common.error.CrossauthError(code: ErrorCode, message: str | list[str] | None = None)[source]

Bases: Exception

Thrown by Crossauth functions whenever it encounters an error.

static as_crossauth_error(e: Any, default_message: str | None = None) CrossauthError[source]

If the passed object is a crossauth_backend.CrossauthError instance, simply returns it. If not and it is an object with errorCode in it, creates a CrossauthError from that and errorMessage, if present. Otherwise creates a crossauth_backend.CrossauthError object with ErrorCode of Unknown from it, setting the message if possible.

Parameters:
  • e (Any) – the error to convert.

  • default_message (str|None) – message to use if there was none in the original exception.

Returns:

a crossauth_backend.CrossauthError instance.

property code_name

Return the name of the error code

static from_oauth_error(error: str, error_description: str | None = None) CrossauthError[source]

OAuth defines certain error types. To convert the error in an OAuth response into a CrossauthError object, call this function.

Parameters:

error (str) – as returned by an OAuth call (converted to an ErrorCode).

:param str error_description as returned by an OAuth call (put in the

message)

:return a crossauth_backend.CrossauthError instance.

static http_status_name(status: str | int) str[source]

Returns the friendly name for an HTTP response code.

If it is not a recognized one, returns the friendly name for 500. @param status the HTTP response code, which, while being numeric,

can be in a string or number.

@returns the string version of the response code.

property oauthErrorCode: str

Return the OAuth name of an error code (eg “server_error”)

class crossauth_backend.common.error.ErrorCode(*values)[source]

Bases: Enum

Indicates the type of error reported by crossauth_backend.CrossauthError

AuthorizationPending = 44

Thrown in the OAuth device code flow

BadRequest = 43

Thrown when an invalid request is made, eg configure 2FA when 2FA is switched off for user

ClientExists = 6

This is returned if attempting to make a client which already exists (client_id or name/userid)

Configuration = 31

Thrown when something is missing or inconsistent in configuration

Connection = 24

Thrown when there is a connection error, eg to a database

ConstraintViolation = 47

Thrown in database handlers where an insert causes a constraint violation

DataFormat = 39

Thrown when a the data field of key storage is not valid json

EmailNotExist = 3

Thrown when a a password reset is requested and the email does not exist

EmailNotVerified = 12

Thrown on login attempt with a user account marked not having had the email address validated

Expired = 23

Thrown when a session or API key has expired

ExpiredToken = 46

Thrown in the OAuth device code flow

Factor2ResetNeeded = 30

Thrown if the user needs to reset factor2 before logging in

FetchError = 40

Thrown if a fetch failed

Forbidden = 19

Returned with an HTTP 403 response

FormEntry = 42

Thrown by user-supplied validation functions if a user details form was incorrectly filled out

InsufficientPriviledges = 18

Returned if user is valid but doesn’t have permission to access resource

InsufficientScope = 17

Thrown for the OAuth insufficient_scope error

InvalidClientId = 5

This is returned if an OAuth2 client id is invalid

InvalidClientIdOrSecret = 8

Server endpoints in this package will return this instead of InvalidClientId or InvalidClientSecret for security purposes

InvalidClientSecret = 7

This is returned if an OAuth2 client secret is invalid

InvalidCsrf = 21

Thrown if the CSRF token is invalid

InvalidEmail = 32

Thrown if an email address in invalid

InvalidHash = 25

Thrown when a hash, eg password, is not in the given format

InvalidKey = 20

Thrown when a session or API key was provided that is not in the key table. For CSRF and sesison key, an InvalidCsrf or InvalidSession will be thrown instead

InvalidOAuthFlow = 10

This is returned a request is made with a an oauth flow name that is not recognized

InvalidPhoneNumber = 33

Thrown if a phone number in invalid

InvalidRedirectUri = 9

This is returned a request is made with a redirect Uri that is not registered

InvalidScope = 16

Thrown for the OAuth invalid_scope error

InvalidSession = 22

Thrown if the session cookie is invalid

InvalidToken = 36

Thrown when a token (eg TOTP or OTP) is invalid

InvalidUsername = 34

Thrown if an email address in invalid

KeyExists = 27

Thrown if you try to create a key which already exists in key storage

MfaRequired = 37

Thrown during OAuth password flow if an MFA step is needed

NotImplemented = 48

Thrown if a method is unimplemented, typically when a feature is not yet supported in this language.

PasswordChangeNeeded = 28

Thrown if the user needs to reset his or her password

PasswordFormat = 38

Thrown when a password does not match rules (length, uppercase/lowercase/digits)

PasswordInvalid = 2

Thrown when a password does not match, eg during login or signup

PasswordMatch = 35

Thrown when two passwords do not match each other (eg signup)

PasswordResetNeeded = 29

Thrown if the user needs to reset his or her password

SlowDown = 45

Thrown in the OAuth device code flow

TwoFactorIncomplete = 13

Thrown on login attempt with a user account marked not having completed 2FA setup

Unauthorized = 14

Thrown when a resource expecting user authorization was access and authorization not provided or wrong

UnauthorizedClient = 15

Thrown for the OAuth unauthorized_client error (when the client is unauthorized as opposed to the user)

UnknownError = 50

Thrown for an condition not convered above.

UnsupportedAlgorithm = 26

Thrown when an algorithm is requested but not supported, eg hashing algorithm

UserExists = 41

Thrown when attempting to create a user that already exists

UserNotActive = 11

Thrown on login attempt with a user account marked inactive

UserNotExist = 1

Thrown when a given username does not exist, eg during login

UsernameOrPasswordInvalid = 4

For endpoints provided by servers in this package, this is returned instead of UserNotExist or PasswordNotMatch, for security reasons

ValueError = 49

Thrown a dict field is unexpectedly missing or wrong type

crossauth_backend.common.interfaces module

class crossauth_backend.common.interfaces.ApiKey[source]

Bases: Key

An API key is a str that can be used in place of a username and password. These are not automatically created, like OAuth access tokens.

created: datetime
data: NotRequired[str]
expires: datetime | NullType
lastactive: NotRequired[datetime]
name: str

A name for the key, unique to the user

userid: NotRequired[str | int | NullType]
value: str
class crossauth_backend.common.interfaces.Key[source]

Bases: TypedDict

A key (eg session ID, email reset token) as stored in a database table.

The fields defined here are the ones used by Crossauth. You may add others.

created: datetime

The datetime/time the key was created, in local time on the server

data: NotRequired[str]

Additional key-specific data (eg new email address for email change).

While application specific, any data Crossauth puts in this field is a strified JSON, with its own key so it can co-exist with other data.

expires: datetime | NullType

The datetime/time the key expires

lastactive: NotRequired[datetime]

The datetime/time key was last used (eg last time a request was made with this value as a session ID)

userid: NotRequired[str | int | NullType]

the user this key is for (or undefined for an anonymous session ID)

It accepts the value null as usually this is the value stored in the database, rather than undefined. Some functions need to differentiate between a null value as opposed to the value not being defined (eg for a partial updatetime).

value: str

The value of the keykey. In a cookie, the value part of cookiename=value; options…

class crossauth_backend.common.interfaces.KeyPrefix[source]

Bases: object

These are used prefixes for keye in a key storage entry

access_token = 'access:'
api_key = 'api:'
authorization_code = 'authz:'
device_code = 'dc:'
email_verification_token = 'e:'
mfa_token = 'omfa:'
password_reset_token = 'p:'
refresh_token = 'refresh:'
session = 's:'
user_code = 'uc:'
class crossauth_backend.common.interfaces.OAuthClient[source]

Bases: TypedDict

OAuth client data as stored in a database table

client_id: str

The client_id, which is auto-generated and immutable

client_name: str

A user-friendly name for the client (not used as part of the OAuth API).

client_secret: NotRequired[str | NullType]

Client secret, which is autogenerated.

If there is no client secret, it should be set to undefined.

This field allows null as well as undefined this is used, for example, when partially updating a client and you specifically want to set the secret to undefined, as opposed to just not wishing to change the value. Other than that, this value is always either a str or undefined.

confidential: bool

Whether or not the client is confidential (and can therefore keep the client secret secret)

redirect_uri: list[str]

An array of value redirect URIs for the client.

userid: NotRequired[str | int | NullType]

ID of the user who owns this client, which may be undefined for not being owned by a specific user.

This field allows null as well as undefined this is used, for example, when partially updating a client and you specifically want to set the user ID to undefined, as opposed to just not wishing to change the value. Other than that, this value is always either a str or number (depending on the ID type in your user storage) or undefined.

valid_flow: list[str]

An array of OAuth flows allowed for this client.

See OAuthFlows.

class crossauth_backend.common.interfaces.PartialKey[source]

Bases: TypedDict

Same as crossauth_backend.Key but all fields are NotRequired

created: datetime
data: str | None
expires: datetime | NullType
lastactive: datetime | None
userid: str | int | NullType | None
value: str
class crossauth_backend.common.interfaces.PartialUser[source]

Bases: PartialUserInputFields

Same as User but all fields are not required

admin: NotRequired[bool]
email: NotRequired[str]
factor1: NotRequired[str]
factor2: NotRequired[str]
id: str | int
state: str
username: str
class crossauth_backend.common.interfaces.PartialUserInputFields[source]

Bases: TypedDict

Same as UserInputFields but all fields are not required

admin: NotRequired[bool]
email: NotRequired[str]
factor1: NotRequired[str]
factor2: NotRequired[str]
state: str
username: str
class crossauth_backend.common.interfaces.PartialUserSecrets[source]

Bases: UserSecretsInputFields

Same as UserSecrets except all fields are NotRequired

expiry: int
otp: str
password: str
totpsecret: str
userid: str | int
class crossauth_backend.common.interfaces.User[source]

Bases: UserInputFields

This adds ID to UserInputFields.

If your username field is unique and immutable, you can omit ID (passing username anywhere an ID) is expected. However, if you want users to be able to change their username, you should include ID field and make that immutable instead.

admin: NotRequired[bool]
email: NotRequired[str]
factor1: str
factor2: NotRequired[str]
id: str | int
state: str
username: str
class crossauth_backend.common.interfaces.UserInputFields[source]

Bases: TypedDict

Describes a user as fetched from the user storage (eg, database table), excluding auto-generated fields such as an auto-generated ID

This is extendible with additional fields - provide them to the UserStorage class as extraFields.

You may want to do this if you want to pass additional user data back to the caller, eg real name.

The fields defined here are the ones used by Crossauth. You may add others.

admin: NotRequired[bool]

Whether or not the user has administrator priviledges (and can acess admin-only functions).

email: NotRequired[str]

You can optionally include an email address field in your user table. If your username is an email address, you do not need a separate field.

factor1: str

Factor for primary authentication.

Should match the name of an authenticator

factor2: NotRequired[str]

Factor for second factor authentication.

Should match the name of an authenticator

state: str

You are free to define your own states. The ones Crossauth recognises are defined in UserState.

username: str

The username. This may be an email address or anything else, application-specific.

class crossauth_backend.common.interfaces.UserSecrets[source]

Bases: UserSecretsInputFields

This adds the user ID toi UserSecretsInputFields.

expiry: int
otp: str
password: str
totpsecret: str
userid: str | int
class crossauth_backend.common.interfaces.UserSecretsInputFields[source]

Bases: TypedDict

Secrets, such as a password, are not in the User object to prevent them accidentally being leaked to the frontend. All functions that return secrets return them in this separate object.

The fields in this class are the ones that are not autogenerated by the database.

expiry: int
otp: str
password: str
totpsecret: str
class crossauth_backend.common.interfaces.UserState[source]

Bases: object

These are used valiues for state in a crossauth_backend.User object

active = 'active'
awaiting_email_verification = 'awaitingemailverification'
awaiting_two_factor_setup = 'awaitingtwofactorsetup'
disabled = 'disabled'
factor2_reset_needed = 'factor2resetneeded'
password_and_factor2_reset_needed = 'passwordandfactor2resetneeded'
password_change_needed = 'passwordchangeneeded'
password_reset_needed = 'passwordresetneeded'
crossauth_backend.common.interfaces.get_json_data(key: Key) Dict[str, Any][source]

Parses the data field in the key as a JSON object

Parameters:

key (crossauth_backend.Key) – the key to extract the data field from

Returns:

the data as dict

crossauth_backend.common.jwt module

class crossauth_backend.common.jwt.JWT(token: str | None = None, payload: Dict[str, Any] = {})[source]

Bases: object

Encapsulates the payload of a JWT, with both the token and decoded JSON payload.

crossauth_backend.common.logger module

class crossauth_backend.common.logger.CrossauthLogger(level: int | None = None)[source]

Bases: CrossauthLoggerInterface

A very simple logging class with no dependencies.

Logs to console.

The logging API is designed so that you can replace this with other common loggers, eg Pino. To change it, use the global CrossauthLogger.set_logger() function. This has a parameter to tell Crossauth whether your logger accepts JSON input or not.

When writing logs, we use the helper function j() to send JSON to the logger if it is supprted, and a stringified JSON otherwise.

Crossauth logs

All Crossauth log messages are JSON (or stringified JSON, depending on whether the logger supports JSON input - this one does). The following fields may be present depending on context (msg is always present):

  • msg : main contents of the log

  • erran error object. If a subclass of Error, it wil contain at least message and

    a stack trace in stack. If the error is of type crossauth_backend.CrossauthError it also will also contain code and http_status.

  • hashedSessionCookiefor security reasons, session cookies are not included in logs.

    However, so that errors can be correlated with each other, a hash of it is included in errors originating from a session.

  • hashedCsrfCookiefor security reasons, csrf cookies are not included in logs.

    However, so that errors can be correlated with each other, a hash of it is included in errors originating from a session.

  • user : username

  • emailMessageId : internal id of any email that is sent

  • email : email address

  • userid : sometimes provided in addition to username, or when username not available

  • hahedApiKeya hash of an API key. The unhashed version is not logged for security,

    but a hash of it is logged for correlation purposes.

  • headeran HTTP header that relates to an error (eg Authorization), only if

    it is non-secret or invalid

  • accessTokenHashhash of the JTI of an access token. For security reasons, the

    unhashed version is not logged.

  • method: request method (GET, PUT etc)

  • url : relevant URL

  • ip : relevant IP address

  • scope : OAuth scope

  • error_code : Crossauth error code

  • error_code_name : String version of Crossauth error code

  • http_status : HTTP status that will be returned

  • port port service is running on (only for starting a service)

  • prefix prefix for endpoints (only when starting a service)

  • authorized whether or not a valid OAuth access token was provided

debug(output: Any) None[source]

Log a debug message

error(output: Any) None[source]

Log an error

info(output: Any) None[source]

Log an info message

levelName = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG']
static logger() CrossauthLoggerInterface[source]

Returns the static logger instance

rossauth_logger_accepts_json = True
set_level(level: int) None[source]

Set the level to report down to

static set_logger(logger: CrossauthLoggerInterface, accepts_json: bool) None[source]

Set the static logger instance

warn(output: Any) None[source]

Log a warning

class crossauth_backend.common.logger.CrossauthLoggerInterface[source]

Bases: object

You can implement your own logger. Crossauth only needs these functions and variables to be present.

Debug = 4
Error = 1
Info = 3
NoLogging = 0
Warn = 2
debug(output: Any) None[source]

Report a message at debug level

error(output: Any) None[source]

Report a message at error level

info(output: Any) None[source]

Report a message at info level

level: int = 0
set_level(level: int) None[source]

Set logging level

warn(output: Any) None[source]

Report a message at warning level

crossauth_backend.common.logger.j(arg: Mapping[str, Any] | str) Dict[str, Any] | str[source]

Helper function that returns JSON if the error log supports it, otherwise a string

Module contents