tailscale_rest/models/user.rs
1//! The people in a tailnet, invitations to join it, and who to email about it.
2
3use crate::model;
4use crate::models::KnownValues;
5
6/// The roles a user can hold.
7///
8/// `owner` is not in [`INVITE_ROLES`]: a tailnet has one, and it is
9/// transferred rather than invited.
10pub const USER_ROLES: &[&str] = &[
11 "owner",
12 "member",
13 "admin",
14 "it-admin",
15 "network-admin",
16 "billing-admin",
17 "auditor",
18];
19
20/// The roles an invitation can offer.
21pub const INVITE_ROLES: &[&str] = &[
22 "member",
23 "admin",
24 "it-admin",
25 "network-admin",
26 "billing-admin",
27 "auditor",
28];
29
30/// Whether a user belongs to this tailnet or is shared into it.
31pub const USER_TYPES: &[&str] = &["member", "shared"];
32
33/// Where a user stands with the tailnet.
34pub const USER_STATUSES: &[&str] = &[
35 "active",
36 "idle",
37 "suspended",
38 "needs-approval",
39 "over-billing-limit",
40];
41
42/// The three addresses a tailnet keeps.
43pub const CONTACT_TYPES: &[&str] = &["account", "support", "security"];
44
45/// [`USER_ROLES`] as a filter, where `all` means do not filter.
46pub const USER_ROLE_FILTERS: &[&str] = &[
47 "owner",
48 "member",
49 "admin",
50 "it-admin",
51 "network-admin",
52 "billing-admin",
53 "auditor",
54 "all",
55];
56
57/// [`USER_TYPES`] as a filter, with the same `all`.
58pub const USER_TYPE_FILTERS: &[&str] = &["member", "shared", "all"];
59
60pub const KNOWN_VALUES: &[KnownValues] = &[
61 ("User.role", USER_ROLES),
62 ("User.type", USER_TYPES),
63 ("User.status", USER_STATUSES),
64 ("UserInvite.role", INVITE_ROLES),
65 ("?contactType", CONTACT_TYPES),
66 ("/tailnet/{tailnet}/users ?role", USER_ROLE_FILTERS),
67 ("/tailnet/{tailnet}/users ?type", USER_TYPE_FILTERS),
68 ("POST /users/{userId}/role body.role", USER_ROLES),
69 ("POST /tailnet/{tailnet}/user-invites body[].role", INVITE_ROLES),
70];
71
72model! {
73 /// A person with access to the tailnet.
74 User {
75 id: "id" => String,
76 display_name: "displayName" => String,
77 login_name: "loginName" => String,
78 profile_pic_url: "profilePicUrl" => String,
79 tailnet_id: "tailnetId" => String,
80 created: "created" => String,
81 /// One of [`USER_TYPES`].
82 user_type: "type" => String,
83 /// One of [`USER_ROLES`].
84 role: "role" => String,
85 /// One of [`USER_STATUSES`].
86 status: "status" => String,
87 device_count: "deviceCount" => i64,
88 last_seen: "lastSeen" => String,
89 currently_connected: "currentlyConnected" => bool,
90 }
91
92 /// An invitation for someone outside the tailnet to join it.
93 UserInvite {
94 id: "id" => String,
95 /// The role the invitee gets on accepting. One of [`INVITE_ROLES`].
96 role: "role" => String,
97 tailnet_id: "tailnetId" => i64,
98 inviter_id: "inviterId" => i64,
99 /// Empty for an invite nobody was mailed, whose URL is shared by hand.
100 email: "email" => String,
101 last_email_sent_at: "lastEmailSentAt" => String,
102 /// Anyone holding this link can accept, not only the addressee.
103 invite_url: "inviteUrl" => String,
104 }
105
106 /// An address the tailnet's notices go to.
107 ///
108 /// There is one of these per contact kind — account, support, security —
109 /// and the kind is in the path rather than in the body.
110 Contact {
111 /// The address in use, which only changes once the new one is verified.
112 email: "email" => String,
113 /// A newly set address that has not been verified yet.
114 fallback_email: "fallbackEmail" => String,
115 needs_verification: "needsVerification" => bool,
116 }
117
118 // -----------------------------------------------------------------------
119 // The shapes the routes carry.
120 // -----------------------------------------------------------------------
121
122 /// The tailnet's users, filtered by whatever the query asked for.
123 UserList as "GET /tailnet/{tailnet}/users 200" {
124 users: "users" => Vec<User>,
125 }
126
127 /// What changing somebody's role sends.
128 UserRole as "POST /users/{userId}/role body" {
129 /// One of [`USER_ROLES`]. A user-owned credential cannot change its
130 /// own holder's role.
131 role: "role" => String,
132 }
133
134 /// One invitation to create, as the request's array carries them.
135 ///
136 /// A create takes a list of these, so several people can be invited in one
137 /// call and each gets its own role.
138 CreateUserInvite as "POST /tailnet/{tailnet}/user-invites body[]" {
139 /// One of [`INVITE_ROLES`], which has no `owner` in it.
140 role: "role" => String,
141 /// Omit to create an invite nobody is mailed, whose `inviteUrl` is
142 /// then shared by hand.
143 email: "email" => String,
144 }
145
146 /// All three contacts at once, keyed by kind.
147 ///
148 /// Not a map, because the kinds are fixed: an answer with a fourth key
149 /// would be the description having changed rather than a tailnet having
150 /// more contacts.
151 Contacts as "GET /tailnet/{tailnet}/contacts 200" {
152 account: "account" => Contact,
153 support: "support" => Contact,
154 security: "security" => Contact,
155 }
156
157 /// What changing one contact sends. The kind is in the path.
158 UpdateContact as "PATCH /tailnet/{tailnet}/contacts/{contactType} body" {
159 /// Setting this mails a verification link; until it is followed the
160 /// old address stays in use and this one is the `fallbackEmail`.
161 email: "email" => String,
162 }
163}