Skip to content

Token

Represents and handles operations for an individual Discord bot token.

A bot token is essentially the "key" to a bot's Discord account. It's used for authorizing Discord API requests and carries all of the bot's permissions, making it a highly sensitive piece of data. It should never be shared with other people or checked into any kind of version control system.

This class effectively combines the functionality provided by Secret (its parent class) and a CliSession (passed in upon instantiation) to deliver a secure and user-friendly interface for creating and accessing files containing encrypted Discord bot tokens.

Diagram - Class Relationships & Structure

classDiagram
    direction BT
    class Token {
        cli: CliSession
        resolve(allow_token_creation)
    }
    class Botstrap {
        _tokens_by_uid: dict[str, Token]
        _active_token: Token | None
        register_token(...)
        parse_args(...)
        retrieve_active_token(...)
        run_bot(...)
    }
    class Secret {
        uid: str
        requires_password: bool
        display_name: str
        storage_directory: Path
        file_path: Path
        min_pw_length: int
        clear()
        read(password)
        validate(data)
        write(data, password)
    }
    class CliSession {
        name: str
        colors: CliColors
        strings: CliStrings
        confirm_or_exit(...)
        exit_process(...)
        get_bool_input(...)
        get_hidden_input(...)
        get_input(...)
        print_prefixed(...)
    }

    Token --|> Secret : inheritance
    Token "*" ..* "1" Botstrap : composition
    Botstrap --|> CliSession : inheritance
    CliSession "1" ..o "*" Token : aggregation

    link Token "#"
    link Botstrap "../../api/botstrap/"
    link CliSession "../cli-session/"
    link Secret "../secret/"

__init__(cli, uid, requires_password=False, display_name=None, storage_directory=None)

Parameters:

Name Type Description Default
cli CliSession

A CliSession providing the UX used by the CLI.

required
uid str

A unique string identifying this token. Will be used in the names of the encrypted .key files containing this token's data.

required
requires_password bool

Whether a user-provided password is required to store and subsequently retrieve the token value.

False
display_name str | None

A human-readable string describing this token. Will be displayed in the CLI when referring to this token. If omitted, the uid will be shown instead.

None
storage_directory str | Path | None

Where to store the encrypted files containing this token's data. If omitted, the files will be saved in a default .botstrap_keys directory.

None

get_default(cli: CliSession) -> Token classmethod

Creates and returns a default token for the provided CliSession.

This token will use the string literal "default" for its uid, and will rely on the default values for all subsequent __init__() parameters.

Parameters:

Name Type Description Default
cli CliSession

A CliSession providing the UX used by the CLI.

required

Returns:

Type Description
Token

A token named "default", created using the default constructor parameters.

resolve(allow_token_creation: bool = True) -> str | None

Returns the value of this token, interactively prompting for input if needed.

The main advantage of this method over the superclass methods read() and write() is that it can interact with the user via the CLI and take different code paths according to their input.

Info - Tasks performed by this method

Based on a combination of this token's state and the input provided by the user, this method can:

  • Automatically decrypt and return the token's value, if no password is required
  • Prompt the user for a password if required by the token, then return the decrypted value if successful
  • Ask whether the user wants to create and encrypt a new file for the token, if it doesn't already exist
  • Walk the user through the process of creating a new token file (and its password, if required)
  • Notify the user about errors (in case of password mismatches, missing files, etc.)
  • Let the user choose whether to continue or exit the process at various points throughout this flow
Example - Creating a password-protected token
Console Session
$ python examplebot.py prod

examplebot: You currently don't have a saved production bot token.
Would you like to add one now? If so, type "yes" or "y": y

Please enter your bot token now. It'll be hidden for security reasons.
BOT TOKEN: ************************.******.***************************

To keep your bot token extra safe, it must be encrypted with a password.
This password won't be stored anywhere. It will only be used as a key to
decrypt your token every time you run your bot in production mode.

Please enter a password for your production bot token.
PASSWORD: ****

Your password must be at least 8 characters long.
Would you like to try a different one? If so, type "yes" or "y": y
PASSWORD: ********

Please re-enter the same password again to confirm.
PASSWORD: ********

Your token has been successfully encrypted and saved.

Do you want to run your bot with this token now? If so, type "yes" or "y": n

Received a non-affirmative response. Exiting process.

Parameters:

Name Type Description Default
allow_token_creation bool

Whether to interactively prompt the user to create (i.e. add and encrypt) the file for this token, if it hasn't already been created. If this is False and the file doesn't exist, this method will return None.

True

Returns:

Type Description
str | None

The token value if it exists and can be decrypted, otherwise None.

Source code in botstrap/internal/tokens.py
Python
def resolve(self, allow_token_creation: bool = True) -> str | None:
    """Returns the value of this token, interactively prompting for input if needed.

    The main advantage of this method over the superclass methods
    [`read()`][botstrap.internal.Secret.read] and
    [`write()`][botstrap.internal.Secret.write] is that it can interact with
    the user via the CLI and take different code paths according to their input.

    ??? info "Info - Tasks performed by this method"
        Based on a combination of this token's state and the input provided by the
        user, this method can:

        - [x] Automatically decrypt and return the token's value, if no password
              is required
        - [x] Prompt the user for a password if required by the token, then
              return the decrypted value if successful
        - [x] Ask whether the user wants to create and encrypt a new file for
              the token, if it doesn't already exist
        - [x] Walk the user through the process of creating a new token file
              (and its password, if required)
        - [x] Notify the user about errors (in case of password mismatches,
              missing files, etc.)
        - [x] Let the user choose whether to continue or exit the process at
              various points throughout this flow

    ??? example "Example - Creating a password-protected token"
        ```console title="Console Session"
        $ python examplebot.py prod

        examplebot: You currently don't have a saved production bot token.
        Would you like to add one now? If so, type "yes" or "y": y

        Please enter your bot token now. It'll be hidden for security reasons.
        BOT TOKEN: ************************.******.***************************

        To keep your bot token extra safe, it must be encrypted with a password.
        This password won't be stored anywhere. It will only be used as a key to
        decrypt your token every time you run your bot in production mode.

        Please enter a password for your production bot token.
        PASSWORD: ****

        Your password must be at least 8 characters long.
        Would you like to try a different one? If so, type "yes" or "y": y
        PASSWORD: ********

        Please re-enter the same password again to confirm.
        PASSWORD: ********

        Your token has been successfully encrypted and saved.

        Do you want to run your bot with this token now? If so, type "yes" or "y": n

        Received a non-affirmative response. Exiting process.
        ```

    Args:
        allow_token_creation:
            Whether to interactively prompt the user to create (i.e. add and
            encrypt) the file for this token, if it hasn't already been created.
            If this is `False` and the file doesn't exist, this method will
            return `None`.

    Returns:
        The token value if it exists and can be decrypted, otherwise `None`.
    """
    if self.file_path.is_file():
        if self.requires_password:
            self.cli.print_prefixed(self.cli.strings.p_cue.substitute(token=self))
            password = self.cli.get_hidden_input(self.cli.strings.p_prompt)
        else:
            password = None

        try:
            return self.read(password=password)
        except (InvalidToken, ValueError):
            message = self.cli.strings.t_mismatch.substitute(token=self)
            self.cli.print_prefixed(message, is_error=True)
            if self.requires_password:
                print(self.cli.strings.p_mismatch)
            return None

    if not allow_token_creation:
        message = self.cli.strings.t_missing.substitute(token=self)
        self.cli.print_prefixed(message, is_error=True)
        return None

    self.cli.print_prefixed()
    self.cli.confirm_or_exit(self.cli.strings.t_create.substitute(token=self))

    self.write(
        data=(token := self.get_new_token_value()),
        password=self.get_new_password() if self.requires_password else None,
    )

    print(self.cli.colors.success(self.cli.strings.t_create_success))
    self.cli.confirm_or_exit(self.cli.strings.t_create_use)

    return token