Skip to content

FirebaseAuth

fastagency.ui.mesop.auth.firebase.FirebaseAuth #

FirebaseAuth(
    sign_in_methods: list[Literal["google"]],
    config: FirebaseConfig,
    allowed_users: Union[
        list[str],
        Callable[[dict[str, Any]], bool],
        Literal["all"],
    ],
)

Initialize the Firebase Auth provider.

PARAMETER DESCRIPTION
sign_in_methods

List of authentication methods to enable. Currently only supports ["google"].

TYPE: list[Literal['google']]

config

Firebase configuration containing project settings.

TYPE: FirebaseConfig

allowed_users

Specifies user access control: - List[str]: List of allowed email addresses - Callable: Function taking decoded token and returning boolean - "all": Allows all authenticated users (default)

TYPE: Union[list[str], Callable[[dict[str, Any]], bool], Literal['all']]

RAISES DESCRIPTION
TypeError

If sign_in_methods is not a list

ValueError

If no sign-in methods specified, unsupported methods provided, or GOOGLE_APPLICATION_CREDENTIALS environment variable is missing

Source code in fastagency/ui/mesop/auth/firebase/firebase_auth.py
def __init__(
    self,
    sign_in_methods: list[Literal["google"]],
    config: FirebaseConfig,
    allowed_users: Union[
        list[str], Callable[[dict[str, Any]], bool], Literal["all"]
    ],  # for callable -> pass the whole decoded token (dict)
) -> None:
    """Initialize the Firebase Auth provider.

    Args:
        sign_in_methods: List of authentication methods to enable.
            Currently only supports ["google"].
        config: Firebase configuration containing project settings.
        allowed_users: Specifies user access control:
            - List[str]: List of allowed email addresses
            - Callable: Function taking decoded token and returning boolean
            - "all": Allows all authenticated users (default)

    Raises:
        TypeError: If sign_in_methods is not a list
        ValueError: If no sign-in methods specified, unsupported methods provided,
            or GOOGLE_APPLICATION_CREDENTIALS environment variable is missing
    """
    # mypy check if self is AuthProtocol
    _self: AuthProtocol = self

    self.config = config
    self.allowed_users = allowed_users

    # Validate sign_in_methods type
    if not isinstance(sign_in_methods, list):
        raise TypeError(
            "sign_in_methods must be a list. Example: sign_in_methods=['google']"
        )

    # 2. Remove duplicates
    self.sign_in_methods = list(set(sign_in_methods))

    # 3. Validate sign-in methods
    if not self.sign_in_methods:
        raise ValueError("At least one sign-in method must be specified")

    unsupported_methods = [
        method for method in self.sign_in_methods if method != "google"
    ]
    if unsupported_methods:
        raise ValueError(
            f"Unsupported sign-in method(s): {unsupported_methods}. Currently, only 'google' sign-in is supported."
        )

    if not os.getenv("GOOGLE_APPLICATION_CREDENTIALS"):
        raise ValueError(
            "Error: A service account key is required. Please create one and set the JSON key file path in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. For more information: https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments"
        )

SIGN_IN_MESSAGE class-attribute instance-attribute #

SIGN_IN_MESSAGE = 'Sign in to your account'

UN_AUTHORIZED_ERROR_MESSAGE class-attribute instance-attribute #

UN_AUTHORIZED_ERROR_MESSAGE = "You are not authorized to access this application. Please contact the application administrators for access."

allowed_users instance-attribute #

allowed_users = allowed_users

config instance-attribute #

config = config

sign_in_methods instance-attribute #

sign_in_methods = list(set(sign_in_methods))

auth_component #

auth_component() -> component
Source code in fastagency/ui/mesop/auth/firebase/firebase_auth.py
def auth_component(self) -> me.component:
    styles = MesopHomePageStyles()
    state = me.state(State)
    if state.authenticated_user:
        with me.box(style=styles.logout_btn_container):
            firebase_auth_component(
                on_auth_changed=self.on_auth_changed, config=self.config
            )
    else:
        with me.box(style=styles.login_box):  # noqa: SIM117
            with me.box(style=styles.login_btn_container):
                message = state.auth_error or FirebaseAuth.SIGN_IN_MESSAGE
                me.text(message, style=styles.header_text)
                firebase_auth_component(
                    on_auth_changed=self.on_auth_changed, config=self.config
                )

create_security_policy #

create_security_policy(
    policy: SecurityPolicy,
) -> SecurityPolicy
Source code in fastagency/ui/mesop/auth/firebase/firebase_auth.py
def create_security_policy(self, policy: me.SecurityPolicy) -> me.SecurityPolicy:
    return me.SecurityPolicy(
        dangerously_disable_trusted_types=True,
        allowed_connect_srcs=list(
            set(policy.allowed_connect_srcs or []) | {"*.googleapis.com"}
        ),
        allowed_script_srcs=list(
            set(policy.allowed_script_srcs or [])
            | {
                "*.google.com",
                "https://www.gstatic.com",
                "https://cdn.jsdelivr.net",
            }
        ),
    )

is_authorized #

is_authorized(token: dict[str, Any]) -> bool

Check if the user is authorized based on the token and allowed_users configuration.

PARAMETER DESCRIPTION
token

The decoded Firebase JWT token containing user information. Must include an 'email' field for validation.

TYPE: dict[str, Any]

RETURNS DESCRIPTION
bool

True if the user is authorized, False otherwise.

TYPE: bool

RAISES DESCRIPTION
TypeError

If allowed_users is not of type str, list, or Callable.

ValueError

If email field is missing in the Firebase token.

Source code in fastagency/ui/mesop/auth/firebase/firebase_auth.py
def is_authorized(self, token: dict[str, Any]) -> bool:
    """Check if the user is authorized based on the token and allowed_users configuration.

    Args:
        token: The decoded Firebase JWT token containing user information.
            Must include an 'email' field for validation.

    Returns:
        bool: True if the user is authorized, False otherwise.

    Raises:
        TypeError: If allowed_users is not of type str, list, or Callable.
        ValueError: If email field is missing in the Firebase token.
    """
    # Check if the email is present in token
    email = token.get("email")
    if not email:
        raise ValueError(
            "Invalid response from Firebase: "
            "`email` field is missing in the token"
        )

    # Handle string-based configuration ("all" or single email)
    if isinstance(self.allowed_users, str):
        if self.allowed_users == "all":
            return True
        return email == self.allowed_users

    # Handle list of allowed email addresses
    if isinstance(self.allowed_users, list):
        return email in {
            addr.strip() if isinstance(addr, str) else addr
            for addr in self.allowed_users
        }

    # Handle custom validation function
    if callable(self.allowed_users):
        return self.allowed_users(token)

    raise TypeError(
        "allowed_users must be one of: "
        "str ('all' or email), "
        "list of emails, "
        "or callable taking token dict"
    )

on_auth_changed #

on_auth_changed(e: WebEvent) -> None
Source code in fastagency/ui/mesop/auth/firebase/firebase_auth.py
def on_auth_changed(self, e: mel.WebEvent) -> None:
    state = me.state(State)
    firebase_auth_token = e.value

    if not firebase_auth_token:
        state.authenticated_user = ""
        state.auth_error = None
        return

    decoded_token = auth.verify_id_token(firebase_auth_token)
    if self.is_authorized(decoded_token):
        state.authenticated_user = decoded_token["email"]
        state.auth_error = None
    else:
        state.authenticated_user = ""
        state.auth_error = FirebaseAuth.UN_AUTHORIZED_ERROR_MESSAGE