Repository: aipotheosis-labs/aci
File: backend/aci/common/schemas/security_scheme.py (Python)

Code before:
        ...,
        min_length=1,
        max_length=2048,
        description="The client ID of the OAuth2 client used for the app",
    )
    client_secret: str = Field(
        ...,
        min_length=1,
        max_length=2048,
        description="The client secret of the OAuth2 client used for the app",
    )
    # NOTE: for some OAuth2 app such as google apps, it will still show "ACI.dev" on the authorization page even if the user provides their own OAuth2 app.
    # It's because the domains shown there is determined by the redirect URL.
    # If user needs complete whitelabeling, they need to provide a custom redirect URL (and set it as redirect URL in their OAuth2 app)
    # and forward the OAuth2 callback response to ACI.dev's callback endpoint.
    # e.g, https://my-app.com/v1/linked-accounts/oauth2/callback (set as redirect URL in OAuth2 app) --forward--> https://api.aci.dev/v1/linked-accounts/oauth2/callback
    redirect_url: str | None = Field(
        default=None,
        min_length=1,
        max_length=2048,
        description="Custom redirect URL for OAuth2 callback for complete whitelabeling. "
        "If not provided, ACI.dev's server redirect URL will be used. "
        "When user uses a custom redirect URL, their backend should forward the OAuth2 callback response to ACI.dev's callback endpoint.",
    )

    @field_validator("redirect_url", check_fields=False)
    def validate_redirect_url(cls, v: str | None) -> str | None:
        if v is None:
            return v
        # sanity check: must be http or https
        if not (v.startswith("http") or v.startswith("https")):
            raise ValueError("Redirect URL must start with http or https")
        return v

    # TODO: might need to support "scope" in the future


class NoAuthScheme(BaseModel, extra="forbid"):
    """
    model for security scheme that has no authentication.
    For now it only allows an empty dict, this is clearer and less ambiguous than using {} or None directly.
    We could also add some fields as metadata in the future if needed.
    """

    pass


# NOTE: not necessary but for the sake of consistency and future use
class NoAuthSchemePublic(BaseModel):
    pass


Review comment (refactor):
I'm curious why we set `check_fields` to `False` here. It validates the filed actually exists on the model, even tho the `redirect_url` can be `None`, but the field still exists.

Some use cases for setting `check_fields` to `False` that ChatGPT suggested are:
> 1. Base class validators: When you define a validator on a base class that should apply to fields that will only be defined in subclasses
> 1. Dynamic field scenarios: When fields might be added dynamically or conditionally
> 1. Generic validators: When creating reusable validators that might apply to different models with different field names

Code after:
        ...,
        min_length=1,
        max_length=2048,
        description="The client ID of the OAuth2 client used for the app",
    )
    client_secret: str = Field(
        ...,
        min_length=1,
        max_length=2048,
        description="The client secret of the OAuth2 client used for the app",
    )
    # NOTE: for some OAuth2 app such as google apps, it will still show "ACI.dev" on the authorization page even if the user provides their own OAuth2 app.
    # It's because the domains shown there is determined by the redirect URL.
    # If user needs complete whitelabeling, they need to provide a custom redirect URL (and set it as redirect URL in their OAuth2 app)
    # and forward the OAuth2 callback response to ACI.dev's callback endpoint.
    # e.g, https://my-app.com/v1/linked-accounts/oauth2/callback (set as redirect URL in OAuth2 app) --forward--> https://api.aci.dev/v1/linked-accounts/oauth2/callback
    redirect_url: str | None = Field(
        default=None,
        min_length=1,
        max_length=2048,
        description="Custom redirect URL for OAuth2 callback for complete whitelabeling. "
        "If not provided, ACI.dev's server redirect URL will be used. "
        "When user uses a custom redirect URL, their backend should forward the OAuth2 callback response to ACI.dev's callback endpoint.",
    )

    @field_validator("redirect_url")
    def validate_redirect_url(cls, v: str | None) -> str | None:
        if v is None:
            return v
        # sanity check: must be http or https
        if not (v.startswith("http") or v.startswith("https")):
            raise ValueError("Redirect URL must start with http or https")
        return v

    # TODO: might need to support "scope" in the future


class NoAuthScheme(BaseModel, extra="forbid"):
    """
    model for security scheme that has no authentication.
    For now it only allows an empty dict, this is clearer and less ambiguous than using {} or None directly.
    We could also add some fields as metadata in the future if needed.
    """

    pass


# NOTE: not necessary but for the sake of consistency and future use
class NoAuthSchemePublic(BaseModel):
    pass