package web import ( "context" "errors" "strconv" "time" ) const ( AdminUsersPerPage = 20 AdminMembersPerPage = 25 AdminGroupsPerPage = 25 AdminInvitationsPerPage = 25 AdminListBadges = 5 AdminDetailBadges = 10 ) var ErrAdminNotFound = errors.New("web: no such user, group or invitation") var ( ErrAdminConfirmCode = errors.New("web: the confirmation code does not match") ErrAdminConfirmSpent = errors.New("web: too many wrong confirmation codes") ) type AdminPendingAccount struct { Username string Spent bool ExpiresAt time.Time } type AdminProblem int const ( AdminUsernameGrammar AdminProblem = iota + 1 AdminUsernameTaken AdminDisplayNameInvalid AdminGroupNameGrammar AdminGroupNameTaken AdminDescriptionInvalid AdminUnknownUser AdminUnknownGroup AdminAlreadyMember AdminNotMember AdminPrimaryGroup AdminSelf AdminLastAdmin AdminReferencedGroup AdminInvitationClosed AdminGoogleNotLinked AdminResetLinkDisabled AdminResetLinkPending AdminLabelInvalid AdminExpiryInvalid AdminAdminGroupGrant AdminBatchSize AdminUserPending AdminUnknownBatch AdminSSHKeyDisabled AdminSSHKeyPending ) var adminProblemNames = map[AdminProblem]string{ AdminUsernameGrammar: "the username does not follow the username grammar", AdminUsernameTaken: "the username is taken", AdminDisplayNameInvalid: "the display name is not valid", AdminGroupNameGrammar: "the group name does not follow the group name grammar", AdminGroupNameTaken: "the group name is taken", AdminDescriptionInvalid: "the description is not valid", AdminUnknownUser: "no user has that username", AdminUnknownGroup: "no group has that name", AdminAlreadyMember: "the user is already a member of the group", AdminNotMember: "the user is not a member of the group", AdminPrimaryGroup: "the primary group's membership cannot be changed", AdminSelf: "an administrator cannot do this to their own account", AdminLastAdmin: "the admin group would have no enabled member", AdminReferencedGroup: "the group is named in the configuration", AdminInvitationClosed: "the invitation is no longer pending", AdminGoogleNotLinked: "the user has no linked Google account", AdminResetLinkDisabled: "a reset link cannot be issued to a disabled account", AdminResetLinkPending: "a reset link cannot be issued to an account waiting for its confirmation", AdminLabelInvalid: "a label is empty or not valid", AdminExpiryInvalid: "the expiry is not one of the offered choices", AdminAdminGroupGrant: "an invitation cannot grant the admin group", AdminBatchSize: "the batch is empty or larger than the cap", AdminUserPending: "the account is waiting for its confirmation", AdminUnknownBatch: "no invitation batch has that id", AdminSSHKeyDisabled: "a disabled account takes no new SSH key", AdminSSHKeyPending: "an account waiting for its confirmation takes no SSH key", } func (p AdminProblem) Error() string { if s, ok := adminProblemNames[p]; ok { return "web: admin: " + s } return "web: unknown admin problem " + strconv.Itoa(int(p)) } type AdminAuditEntry struct { At time.Time Actor string Action string Subject string NewSession bool } type AdminDashboard struct { Users int DisabledUsers int Groups int PendingInvitations int LiveSessions int Recent []AdminAuditEntry } type AdminUserRow struct { ID string Username string DisplayName string Disabled bool Pending bool Groups []string GroupCount int } type AdminUserList struct { Users []AdminUserRow Total int } type AdminGroupRef struct { ID string Name string Primary bool } type AdminGoogleLink struct { LinkedAt time.Time } type AdminUser struct { ID string Username string DisplayName string UIDNumber int Disabled bool Pending bool Admin bool CreatedAt time.Time LastLoginAt *time.Time Groups []AdminGroupRef GroupCount int Google *AdminGoogleLink Sessions int RefreshTokens int Activity []AdminAuditEntry SSHKeys []SSHKey } type AdminMemberships struct { Username string Groups []AdminGroupRef Total int Candidates []string } type AdminGroupRow struct { ID string Name string Description string Members int } type AdminGroupList struct { Groups []AdminGroupRow Total int } type AdminMember struct { ID string Username string DisplayName string Pending bool } type AdminGroup struct { ID string Name string Description string GIDNumber int Primary bool Referenced bool Members []AdminMember Total int Disabled []AdminMember DisabledTotal int } type AdminInvitationState int const ( AdminInvitationPending AdminInvitationState = iota + 1 AdminInvitationUsed AdminInvitationRevoked AdminInvitationExpired ) type AdminInvitation struct { ID string CreatedBy string CreatedAt time.Time ExpiresAt time.Time State AdminInvitationState Label string UsedBy string UsedByID string UsedByPending bool UsedByDeleted bool Batch string Groups []AdminInvitationGroup } type AdminInvitationGroup struct { Name string Deleted bool } type AdminInvitationList struct { Invitations []AdminInvitation Total int } var AdminInvitationExpiries = []time.Duration{24 * time.Hour, 7 * 24 * time.Hour, 30 * 24 * time.Hour} const AdminInvitationExpiryDefault = 7 * 24 * time.Hour type AdminNewInvitations struct { Labels []string GroupIDs []string GroupNames []string Expiry time.Duration } type AdminIssuedInvitation struct { Label string URL string } type AdminCreatedInvitations struct { Invitations []AdminIssuedInvitation ExpiresAt time.Time Groups []string } type AdminGroupChoice struct { ID string Name string } type AdminGroupChoices struct { Groups []AdminGroupChoice More bool Defaults []string } type AdminNewUser struct { Username string DisplayName string } type AdminProfile struct { DisplayName string } type AdminNewGroup struct { Name string Description string } type AdminBackend interface { Dashboard(ctx context.Context) (AdminDashboard, error) ListUsers(ctx context.Context, query string, page int) (AdminUserList, error) User(ctx context.Context, id string) (AdminUser, error) CreateUser(ctx context.Context, actor string, u AdminNewUser) (string, error) UpdateProfile(ctx context.Context, actor, id string, p AdminProfile) error DisableUser(ctx context.Context, actor, id string) error EnableUser(ctx context.Context, actor, id string) error RevokeAllSessions(ctx context.Context, actor, id string) error UnlinkGoogle(ctx context.Context, actor, id string) error DeleteUser(ctx context.Context, actor, id string) error Memberships(ctx context.Context, userID string, page int) (AdminMemberships, error) AddMembership(ctx context.Context, actor, userID, groupName string) error AddMember(ctx context.Context, actor, groupID, username string) error RemoveMembership(ctx context.Context, actor, userID, groupID string) error ListGroups(ctx context.Context, page int) (AdminGroupList, error) Group(ctx context.Context, id string, page int) (AdminGroup, error) GroupCandidates(ctx context.Context, groupID, query string) ([]AdminMember, error) CreateGroup(ctx context.Context, actor string, g AdminNewGroup) (string, error) DescribeGroup(ctx context.Context, actor, id, description string) error DeleteGroup(ctx context.Context, actor, id string) error ListInvitations(ctx context.Context, page int) (AdminInvitationList, error) Invitation(ctx context.Context, id string) (AdminInvitation, error) CreateInvitations(ctx context.Context, actor string, n AdminNewInvitations) (AdminCreatedInvitations, error) InvitationGroupChoices(ctx context.Context) (AdminGroupChoices, error) PendingAccount(ctx context.Context, id string) (AdminPendingAccount, error) ConfirmAccount(ctx context.Context, actor, id, code string) error RejectAccount(ctx context.Context, actor, id string) error RevokeInvitation(ctx context.Context, actor, id string) error RevokeInvitationBatch(ctx context.Context, actor, batch string) error IssueResetLink(ctx context.Context, actor, id string) (AdminResetLink, error) AddSSHKey(ctx context.Context, actor, id, key, label string) (SSHKey, error) RemoveSSHKey(ctx context.Context, actor, id, keyID string) (SSHKey, error) Audit(ctx context.Context, q AdminAuditQuery) (AdminAuditPage, error) }