# `Macaw.Auth.Native.UserRecord`
[🔗](https://github.com/dcoai/macaw_auth/blob/v0.4.1/lib/macaw/auth/native/user_record.ex#L1)

A row of `macaw_auth_users`, the native backend's users, in the host's
repo (`mix macaw_auth.gen.migration` writes the table's migration).

- `username` is stored normalized (`normalize_username/1`: trimmed,
  lower case), with a unique index, so `Alice` and `alice ` are one
  user on every database without a case-insensitive collation.
- `hashed_password` is nil for a directory user: the directory checks
  their password, and the `Password` source does not know them.
- `source` names where the identity came from: `"local"` (the
  `Password` source) or an LDAP source's name, which owns the row and
  re-derives its roles at each sign-in.
- `roles` is `{:array, :string}`: a native array on Postgres, and on
  SQLite a JSON text column, which ecto_sqlite3 encodes and decodes for
  an array field itself. One migration serves both; no query needs to
  look inside the list.
- `disabled_at` set means refused, whatever the password.
- `extra` holds what a source knows beyond these (an LDAP DN, its id
  attribute, email) and the host's own fields.

- `totp_secret_sealed`, `totp_enabled_at`, `totp_last_step`: the second
  factor (`Macaw.Auth.Native.Totp`), added by their own migration. The
  secret is sealed (`Macaw.Auth.Native.Seal`), never stored as it is;
  an enrollment is pending while `totp_enabled_at` is nil; the last
  accepted step is what makes a code single-use. None of them is cast
  by `changeset/2`: only `Totp` writes them, each by a conditional
  update.

# `t`

```elixir
@type t() :: %Macaw.Auth.Native.UserRecord{
  __meta__: term(),
  disabled_at: term(),
  display_name: term(),
  extra: term(),
  hashed_password: term(),
  id: term(),
  inserted_at: term(),
  must_change_password: term(),
  roles: term(),
  source: term(),
  totp_enabled_at: term(),
  totp_last_step: term(),
  totp_secret_sealed: term(),
  updated_at: term(),
  username: term()
}
```

# `changeset`

```elixir
@spec changeset(t(), map()) :: Ecto.Changeset.t()
```

A changeset for a new or changed user; the username is normalized.

# `normalize_username`

```elixir
@spec normalize_username(term()) :: {:ok, String.t()} | {:error, :invalid_username}
```

A username trimmed and lower-cased: `{:ok, name}`, or
`{:error, :invalid_username}` unless it is 1–64 characters of
`[a-z0-9._@+-]`, starting with a letter or digit. Nothing else is
rewritten, so two distinct inputs never become one user.

---

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