crates/nornir-identity/proto/nornir/identity/v1/identity.proto
// The ONE identity service every nordisk product serves (IDENTITY-HOST,
// 2026-10-27). gunnar-server and holger-server mount the same adapter
// (`nornir_identity::grpc::IdentityGrpc`), so a console, a CLI or a test sees
// the same answers from either product.
//
// A refusal is a gRPC status whose message starts with the refusal's stable
// code (`signup_closed: ...`), and whose `nornir-identity-refusal` metadata is
// that code alone; the product's words are the console's to choose.
//
// A session is an opaque token: the server keeps only its hash. A relay (a
// console) carries it; it decides nothing.
syntax = "proto3";
package nornir.identity.v1;
service IdentityService {
// Self-service sign-up: open on a Root site, refused (`signup_closed`) on a
// Private one. A first-login link goes to the address; the reply is the same
// whether or not the address already had an account.
rpc SignUp(SignUpRequest) returns (SignUpReply);
// An admin's single-use enrolment invite. Never grants an admin. With
// `person`: a passkey link for that existing person (an admin for anybody,
// a person for themself).
rpc CreateInvite(CreateInviteRequest) returns (CreateInviteReply);
// The open invites this session may see (an admin: every one; anybody else:
// the ones they minted and the ones for them). Never a secret.
rpc ListInvites(ListInvitesRequest) returns (ListInvitesReply);
// Withdraw an open invite (an admin: any; anybody else: their own).
rpc RevokeInvite(RevokeInviteRequest) returns (RevokeInviteReply);
// Open the passkey creation ceremony that redeems an invite or a first-login link.
rpc RedeemStart(RedeemStartRequest) returns (CeremonyReply);
// Finish it: the invite is spent, the passkey stored, a session minted.
rpc RedeemFinish(RedeemFinishRequest) returns (SessionReply);
// Open a passkey login (discoverable credentials).
rpc LoginStart(LoginStartRequest) returns (CeremonyReply);
// Verify the assertion and mint a session.
rpc LoginFinish(LoginFinishRequest) returns (SessionReply);
// Who a session is, or `no_session`.
rpc Whoami(WhoamiRequest) returns (SessionInfo);
// A fresh challenge for a step-up of the session's own passkey.
rpc StepUpStart(StepUpStartRequest) returns (ChallengeReply);
// A fresh user-verified touch of one's own passkey: the session is stepped up.
rpc StepUpFinish(StepUpFinishRequest) returns (SessionInfo);
// End this session, or every session of its person.
rpc Logout(LogoutRequest) returns (LogoutReply);
// An admin (stepped up) deactivates a person: every session of theirs ends.
rpc Deactivate(DeactivateRequest) returns (DeactivateReply);
// An admin (stepped up) ends every session on the site, their own included.
rpc EndAllSessions(EndAllSessionsRequest) returns (EndAllSessionsReply);
}
message SignUpRequest {
string email = 1;
string display = 2;
}
message SignUpReply {}
message CreateInviteRequest {
string session = 1;
// The pinned name, or empty: the invitee chooses one.
string display = 2;
// Empty: no e-mail, the secret is handed over by the admin.
string email = 3;
repeated string groups = 4;
// 0: the default (a day).
int64 ttl_secs = 5;
// An existing person's id: the link adds a passkey to them (no groups, no
// display). Empty: the invite makes a new person.
string person = 6;
}
message CreateInviteReply {
// Shown once.
string secret = 1;
string invite_id = 2;
int64 expires_at = 3;
bool mailed = 4;
// Why the mail did not go (an airgapped site's mailer refuses by name).
string mail_refusal = 5;
}
message RedeemStartRequest {
string secret = 1;
// The name the invitee chooses, when the invite pinned none.
string display = 2;
string rp_id = 3;
string origin = 4;
// The browser's address as the relay saw it (empty: the caller's).
string client = 5;
}
message LoginStartRequest {
string rp_id = 1;
string origin = 2;
string client = 3;
}
message CeremonyReply {
string ceremony_id = 1;
// base64url, as clientDataJSON carries it.
string challenge = 2;
string rp_id = 3;
// Creation only: the user handle and names the authenticator stores.
string user_id = 4;
string user_name = 5;
string display = 6;
bool user_verification_required = 7;
int64 expires_at_ms = 8;
// Creation for an existing person: the passkeys they hold, for
// `excludeCredentials` (a device must not replace one).
repeated bytes exclude_credentials = 9;
}
message RedeemFinishRequest {
string ceremony_id = 1;
bytes credential_id = 2;
// P-256, SEC1 uncompressed.
bytes public_key = 3;
bytes client_data_json = 4;
bytes attestation_object = 5;
string rp_id = 6;
string origin = 7;
}
message LoginFinishRequest {
string ceremony_id = 1;
bytes credential_id = 2;
bytes client_data_json = 3;
bytes authenticator_data = 4;
bytes signature = 5;
string rp_id = 6;
string origin = 7;
}
message SessionReply {
// The opaque session token: the relay carries it, the server keeps its hash.
string token = 1;
SessionInfo session = 2;
}
message SessionInfo {
string user_id = 1;
string display = 2;
repeated string roles = 3;
bool admin = 4;
int64 expires_at = 5;
bool stepped_up = 6;
}
message WhoamiRequest {
string session = 1;
}
message StepUpStartRequest {
string session = 1;
string client = 2;
}
message ChallengeReply {
string challenge = 1;
int64 expires_at_ms = 2;
}
message StepUpFinishRequest {
string session = 1;
bytes credential_id = 2;
bytes client_data_json = 3;
bytes authenticator_data = 4;
bytes signature = 5;
string rp_id = 6;
string origin = 7;
}
message LogoutRequest {
string session = 1;
// Every session of this person, on every device.
bool everywhere = 2;
}
message LogoutReply {}
message DeactivateRequest {
string session = 1;
string user_id = 2;
}
message DeactivateReply {}
message ListInvitesRequest {
string session = 1;
}
message InviteInfo {
// The secret's hash: the id to withdraw it by.
string invite_id = 1;
string display = 2;
repeated string groups = 3;
// The existing person it adds a passkey to; empty: a new person.
string person = 4;
string created_by = 5;
int64 created_at = 6;
int64 expires_at = 7;
}
message ListInvitesReply {
repeated InviteInfo invites = 1;
}
message RevokeInviteRequest {
string session = 1;
string invite_id = 2;
}
message RevokeInviteReply {}
message EndAllSessionsRequest {
string session = 1;
}
message EndAllSessionsReply {}