# `KeenAuth`
[🔗](https://github.com/KeenMate/keen_auth/blob/main/lib/keen_auth.ex#L1)

KeenAuth provides a powerful pipeline-based authentication system for Phoenix applications.

The library implements a "super simple yet super powerful" approach with two entry points
(OAuth or Email) that converge into a shared pipeline: Mapper → Processor → Storage.

## Architecture Overview

```
                    ┌─────────────────────────────────┐
                    │         ENTRY POINTS            │
                    └─────────────────────────────────┘
                                    │
            ┌───────────────────────┴───────────────────────┐
            │                                               │
            ▼                                               ▼
  ┌───────────────────┐                         ┌───────────────────┐
  │   OAuth (Assent)  │                         │   Email (Custom)  │
  │                   │                         │                   │
  │ External provider │                         │ Your app verifies │
  │ verifies creds    │                         │ email/password    │
  └─────────┬─────────┘                         └─────────┬─────────┘
            │                                             │
            │         {:ok, raw_user}                     │
            └───────────────────┬─────────────────────────┘
                                │
                                ▼
                    ┌─────────────────────────────────┐
                    │       KEEN AUTH PIPELINE        │
                    └─────────────────────────────────┘
                                │
                                ▼
                    ┌─────────────────────────────────┐
                    │  MAPPER                         │
                    │  Normalize raw_user → User      │
                    └─────────────────────────────────┘
                                │
                                ▼
                    ┌─────────────────────────────────┐
                    │  PROCESSOR                      │
                    │  Business logic, DB, roles      │
                    └─────────────────────────────────┘
                                │
                                ▼
                    ┌─────────────────────────────────┐
                    │  STORAGE                        │
                    │  Persist session/tokens         │
                    └─────────────────────────────────┘
```

## Entry Points

- **OAuth** (via Assent): External providers handle credential verification
- **Email**: Your app implements `KeenAuth.EmailAuthenticationHandler` to verify credentials

Both entry points produce a `raw_user` map that flows through the same pipeline.

## Pipeline Stages

- **Mapper**: Normalizes user data and can enrich with external API calls
- **Processor**: Implements business logic, validation, and user transformations
- **Storage**: Manages data persistence (sessions, database, JWT, custom)

## Basic Configuration

```elixir
config :keen_auth,
  strategies: [
    azure_ad: [
      strategy: Assent.Strategy.AzureAD,
      mapper: KeenAuth.Mappers.AzureAD,
      processor: MyApp.Auth.Processor,
      config: [
        tenant_id: System.get_env("AZURE_TENANT_ID"),
        client_id: System.get_env("AZURE_CLIENT_ID"),
        client_secret: System.get_env("AZURE_CLIENT_SECRET"),
        redirect_uri: "https://myapp.com/auth/azure_ad/callback"
      ]
    ],
    github: [
      strategy: Assent.Strategy.Github,
      mapper: KeenAuth.Mappers.Github,
      processor: MyApp.Auth.Processor,
      config: [
        client_id: System.get_env("GITHUB_CLIENT_ID"),
        client_secret: System.get_env("GITHUB_CLIENT_SECRET"),
        redirect_uri: "https://myapp.com/auth/github/callback"
      ]
    ]
  ]
```

## Router Integration

Add authentication routes using the macro:

```elixir
defmodule MyAppWeb.Router do
  require KeenAuth

  scope "/auth" do
    pipe_through :browser
    KeenAuth.authentication_routes()
  end
end
```

## Usage

Check authentication status and get current user:

```elixir
if KeenAuth.authenticated?(conn) do
  user = KeenAuth.current_user(conn)
  # User is authenticated
else
  # Redirect to login
end
```

# `user`

```elixir
@type user() :: KeenAuth.User.t() | map() | term()
```

# `assign_current_user`

```elixir
@spec assign_current_user(Plug.Conn.t(), user()) :: Plug.Conn.t()
```

Assigns a user to the connection.

This function is typically used internally by the authentication pipeline,
but can be useful for testing or manual user assignment.

## Examples

    iex> conn = KeenAuth.assign_current_user(conn, user)
    iex> KeenAuth.current_user(conn)
    %{id: 123, email: "user@example.com"}

# `authenticated?`

```elixir
@spec authenticated?(Plug.Conn.t()) :: boolean()
```

Checks if the current connection has an authenticated user.

Returns `true` if a user is assigned to the connection, `false` otherwise.

## Examples

    iex> KeenAuth.authenticated?(conn_with_user)
    true

    iex> KeenAuth.authenticated?(conn_without_user)
    false

# `authentication_routes`
*macro* 

Generates authentication routes for the router.

This macro creates the necessary routes for OAuth authentication flows:
- `GET /auth/:provider/new` - Initiates OAuth flow
- `GET /auth/:provider/callback` - Handles OAuth callback
- `POST /auth/:provider/callback` - Handles OAuth callback (POST)
- `GET /auth/:provider/delete` - Signs out user
- `GET /auth/delete` - Signs out user (provider-agnostic)

Optionally includes email authentication routes if `:email_enabled` is configured.

## Example

    defmodule MyAppWeb.Router do
      require KeenAuth

      scope "/auth" do
        pipe_through :browser
        KeenAuth.authentication_routes()
      end
    end

This will create routes like:
- `/auth/azure_ad/new`
- `/auth/github/callback`
- `/auth/delete`

# `current_user`

```elixir
@spec current_user(Plug.Conn.t()) :: user()
```

Returns the current authenticated user from the connection.

Retrieves the user that was assigned to the connection during the authentication
process, typically by `KeenAuth.Plug.FetchUser`.

## Examples

    iex> KeenAuth.current_user(conn)
    %{id: 123, email: "user@example.com", name: "John Doe"}

    iex> KeenAuth.current_user(unauthenticated_conn)
    nil

# `list_providers`

```elixir
@spec list_providers(Plug.Conn.t() | atom()) :: [map()]
```

Returns a list of configured authentication providers with metadata.

Useful for dynamically rendering login pages with only the providers
that are actually configured.

## Variants

- `list_providers(conn)` - Use when you have a connection that went through `KeenAuth.Plug`
- `list_providers(otp_app)` - Use when you don't have a connection (e.g., login page before auth pipeline)

## Options

Each provider can include optional metadata in its configuration:
- `:enabled` - Set to `false` to hide provider from list (defaults to `true`)
- `:label` - Display name (defaults to provider name capitalized)
- `:icon` - Icon identifier or URL
- `:color` - Brand color for styling

## Example Configuration

    config :my_app, :keen_auth,
      strategies: [
        email: [
          label: "Email",
          icon: "mail",
          authentication_handler: MyApp.Auth.EmailHandler,
          ...
        ],
        entra: [
          label: "Microsoft",
          icon: "microsoft",
          color: "#0078d4",
          strategy: Assent.Strategy.AzureAD,
          ...
        ],
        github: [
          label: "GitHub",
          icon: "github",
          color: "#333",
          strategy: Assent.Strategy.Github,
          ...
        ]
      ]

## Example Usage

    # With connection (after going through KeenAuth.Plug pipeline)
    providers = KeenAuth.list_providers(conn)

    # Without connection (e.g., login page)
    providers = KeenAuth.list_providers(:my_app)

    # Returns:
    [
      %{name: :email, label: "Email", icon: "mail", path: "/auth/email/new", color: nil},
      %{name: :entra, label: "Microsoft", icon: "microsoft", path: "/auth/entra/new", color: "#0078d4"},
      %{name: :github, label: "GitHub", icon: "github", path: "/auth/github/new", color: "#333"}
    ]

    # In your template
    <%= for provider <- @providers do %>
      <a href={provider.path} style={"background: #{provider.color}"}>
        <i class={"icon-#{provider.icon}"}></i>
        <%= provider.label %>
      </a>
    <% end %>

# `provider_names`

```elixir
@spec provider_names(Plug.Conn.t()) :: [atom()]
```

Returns a list of configured provider names (atoms).

Simpler alternative to `list_providers/1` when you only need the names.

## Example

    KeenAuth.provider_names(conn)
    #=> [:email, :entra, :github]

# `render_providers`

```elixir
@spec render_providers([map()], (map() -&gt; String.t())) :: String.t()
```

Renders provider buttons using a custom callback function.

This function takes a list of providers (from `list_providers/1`) and a render
callback that returns HTML for each provider. This allows the same provider list
to be rendered differently in different contexts.

## Parameters

- `providers` - List of provider maps from `list_providers/1`
- `render_fn` - Function that takes a provider map and returns an HTML string

## Provider Map Fields

The callback receives a map with these fields:
- `:name` - Provider atom (e.g., `:github`, `:entra`)
- `:label` - Display name (e.g., "GitHub", "Microsoft Entra")
- `:icon` - Icon identifier (if configured)
- `:color` - Brand color (if configured)
- `:path` - Authentication path (e.g., "/auth/github/new")

## Examples

    providers = KeenAuth.list_providers(:my_app)

    # Small inline buttons for navbar
    KeenAuth.render_providers(providers, fn p ->
      ~s(<a href="#{p.path}" class="btn btn-sm">#{p.label}</a>)
    end)

    # Large buttons with icons for login page
    KeenAuth.render_providers(providers, fn p ->
      ~s'''
      <a href="#{p.path}" class="login-btn" style="background: #{p.color || "#333"}">
        <i class="icon-#{p.icon}"></i>
        <span>#{p.label}</span>
      </a>
      '''
    end)

    # Filter OAuth providers only (exclude email)
    providers
    |> Enum.reject(& &1.name == :email)
    |> KeenAuth.render_providers(fn p ->
      ~s(<button onclick="location.href='#{p.path}'">#{p.label}</button>)
    end)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
