Definition & Overview
A user is the fundamental identity entity within the Prismatic Platform, representing an authenticated individual who interacts with the system. Each user has a unique identifier, a set of assigned roles that determine their access level, a collection of permissions that fine-tune their capabilities, and an activity history that tracks their interactions with platform features. Users are the subjects of all authorization decisions, the owners of all tracked progress, and the actors behind all audited operations.
The user model in the Prismatic Platform extends beyond simple authentication to encompass the full lifecycle of platform interaction. From their first login, users accumulate a rich profile: Academy learning progress across topics and tracks, OSINT tool execution history, DD entity exploration patterns, and session context that persists across Claude Code interactions. This comprehensive user model enables personalized experiences like track recommendations based on knowledge gaps and frequently-used tool shortcuts.
User management follows the principle of least privilege, where users are granted the minimum permissions necessary for their role. The platform's RBAC (Role-Based Access Control) system supports hierarchical roles (viewer, analyst, administrator, operator) with fine-grained permissions that can be scoped to specific resources. This ensures that an analyst can execute OSINT tools but cannot modify system configuration, while an administrator has full platform control.
Technical Deep Dive
The user entity in the platform is represented as an Ecto schema with role-based access control:
defmodule PrismaticAuth.User do
@moduledoc """
User entity with role-based access control, supporting
authentication, authorization, and activity tracking.
"""
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{
id: Ecto.UUID.t(),
email: String.t(),
name: String.t(),
roles: [String.t()],
permissions: [String.t()],
active: boolean(),
last_login_at: DateTime.t() | nil,
metadata: map()
}
@primary_key {:id, :binary_id, autogenerate: true}
schema "users" do
field :email, :string
field :name, :string
field :password_hash, :string, redact: true
field :roles, {:array, :string}, default: ["viewer"]
field :permissions, {:array, :string}, default: []
field :active, :boolean, default: true
field :last_login_at, :utc_datetime_usec
field :metadata, :map, default: %{}
timestamps(type: :utc_datetime_usec)
end
@required_fields [:email, :name]
@optional_fields [:roles, :permissions, :active, :metadata]
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(user, attrs) do
user
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_format(:email, ~r/^[^\s]+@[^\s]+\.[^\s]+$/)
|> unique_constraint(:email)
|> validate_roles()
end
defp validate_roles(changeset) do
valid_roles = MapSet.new(["viewer", "analyst", "operator", "administrator"])
validate_change(changeset, :roles, fn :roles, roles ->
invalid = Enum.reject(roles, &MapSet.member?(valid_roles, &1))
case invalid do
[] -> []
_ -> [roles: "invalid roles: #{Enum.join(, ", ")}"]
end
end)
end
end
Authorization checks use the user's roles and permissions:
defmodule PrismaticAuth.Authorization do
@moduledoc """
Role-based authorization with hierarchical permissions
and resource-scoped access control.
"""
@role_hierarchy %{
"viewer" => 0,
"analyst" => 1,
"operator" => 2,
"administrator" => 3
}
@role_permissions %{
"viewer" => ["read:tools", "read:academy", "read:glossary"],
"analyst" => ["read:tools", "execute:tools", "read:academy", "write:academy",
"read:dd", "read:perimeter", "read:glossary"],
"operator" => ["read:tools", "execute:tools", "manage:tools",
"read:academy", "write:academy", "manage:academy",
"read:dd", "write:dd", "read:perimeter", "execute:perimeter",
"read:glossary"],
"administrator" => ["*"]
}
@spec authorized?(PrismaticAuth.User.t(), String.t()) :: boolean()
def authorized?(%{roles: roles, permissions: permissions}, required_permission) do
has_wildcard = "*" in effective_permissions(roles, permissions)
has_permission = required_permission in effective_permissions(roles, permissions)
has_wildcard or has_permission
end
@spec has_role?(PrismaticAuth.User.t(), String.t()) :: boolean()
def has_role?(%{roles: roles}, required_role) do
required_level = Map.get(@role_hierarchy, required_role, 0)
Enum.any?(roles, fn role ->
Map.get(@role_hierarchy, role, 0) >= required_level
end)
end
defp effective_permissions(roles, extra_permissions) do
role_perms =
roles
|> Enum.flat_map(fn role -> Map.get(@role_permissions, role, []) end)
MapSet.new(role_perms ++ extra_permissions)
|> MapSet.to_list()
end
endArchitecture & Implementation
The user system integrates with multiple platform subsystems:
Authentication Layer: Users authenticate via email/password or API tokens. The authentication layer produces JWT tokens containing the user's ID, roles, and permissions. These tokens are verified by the APIAuth plug on every request to the PrismaticAPI gateway and by the LiveView session system for web interactions.
Session Management: Phoenix LiveView sessions are bound to authenticated users. The session carries user context (ID, roles, preferences) that persists across page navigations without re-authentication. Session state is stored server-side with only an opaque session ID transmitted to the client.
Activity Tracking: Every significant user action is recorded: OSINT tool executions, Academy topic completions, DD entity explorations, and Perimeter scan requests. This activity data feeds the recommendation engine and provides audit trails for compliance reporting.
Profile Enrichment: The user profile accumulates derived data over time: most-used OSINT tools, Academy completion percentage, preferred tool categories, and active learning tracks. This enrichment happens asynchronously through telemetry event handlers to avoid impacting request latency.
defmodule PrismaticAuth.UserContext do
@moduledoc """
Builds rich user context from authentication data,
combining identity with preferences and activity.
"""
@spec build(String.t()) :: {:ok, map()} | {:error, :not_found}
def build(user_id) do
with {:ok, user} <- PrismaticAuth.Users.get(user_id),
progress <- PrismaticAcademy.ProgressTracker.get_progress(user_id),
activity <- PrismaticTracking.EventCollector.get_summary(user_id) do
context = %{
user: user,
academy_progress: progress,
recent_tools: activity.recent_tools,
preferred_categories: activity.preferred_categories,
session_count: activity.session_count
}
{:ok, context}
end
end
endUsage in Prismatic Platform
Users interact with the platform through LiveView dashboards that adapt based on their role and preferences:
defmodule PrismaticWeb.UserLive.Profile do
use PrismaticWeb, :live_view
@impl true
def mount(_params, session, socket) do
user_id = session["user_id"]
case PrismaticAuth.UserContext.build(user_id) do
{:ok, context} ->
{:ok, assign(socket,
user: context.user,
progress: context.academy_progress,
recent_tools: context.recent_tools,
preferred_categories: context.preferred_categories
)}
{:error, :not_found} ->
{:ok, redirect(socket, to: "/login")}
end
end
end
The authorization system protects routes and actions throughout the platform, ensuring that users can only access features appropriate to their role. The OSINT toolbox checks execute:tools permission before allowing tool execution. The DD pipeline checks write:dd permission before allowing entity modifications.
Cross-References
- Token - Authentication credential
- Authentication - Identity verification
- Authorization - Access control
- RBAC - Role-Based Access Control
- Tracking - User activity monitoring
Created by Tomas Korcak (korczis) | GitHub