Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

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 {}