325 lines
9.3 KiB
Go
325 lines
9.3 KiB
Go
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
|
//
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
package iam
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/mail"
|
|
)
|
|
|
|
type ErrInvalidToken struct{ message string }
|
|
|
|
func NewInvalidTokenError() error {
|
|
return &ErrInvalidToken{"invalid invitation token"}
|
|
}
|
|
|
|
func (e ErrInvalidToken) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
type ErrInvitationAlreadyAccepted struct{ InvitationID gid.GID }
|
|
|
|
func NewInvitationAlreadyAcceptedError(invitationID gid.GID) error {
|
|
return &ErrInvitationAlreadyAccepted{InvitationID: invitationID}
|
|
}
|
|
|
|
func (e ErrInvitationAlreadyAccepted) Error() string {
|
|
return fmt.Sprintf("invitation %q already accepted", e.InvitationID)
|
|
}
|
|
|
|
type ErrInvitationNotFound struct{ InvitationID gid.GID }
|
|
|
|
func NewInvitationNotFoundError(invitationID gid.GID) error {
|
|
return &ErrInvitationNotFound{InvitationID: invitationID}
|
|
}
|
|
|
|
func (e ErrInvitationNotFound) Error() string {
|
|
return fmt.Sprintf("invitation %q not found", e.InvitationID)
|
|
}
|
|
|
|
type ErrInvitationExpired struct{ InvitationID gid.GID }
|
|
|
|
func NewInvitationExpiredError(invitationID gid.GID) error {
|
|
return &ErrInvitationExpired{InvitationID: invitationID}
|
|
}
|
|
|
|
func (e ErrInvitationExpired) Error() string {
|
|
return fmt.Sprintf("invitation %q expired", e.InvitationID)
|
|
}
|
|
|
|
type ErrIdentityAlreadyExists struct{ EmailAddress mail.Addr }
|
|
|
|
func NewIdentityAlreadyExistsError(emailAddress mail.Addr) error {
|
|
return &ErrIdentityAlreadyExists{EmailAddress: emailAddress}
|
|
}
|
|
|
|
func (e ErrIdentityAlreadyExists) Error() string {
|
|
return fmt.Sprintf("identity %q already exists", e.EmailAddress.String())
|
|
}
|
|
|
|
type ErrEmailAlreadyVerified struct{ message string }
|
|
|
|
func NewEmailAlreadyVerifiedError() error {
|
|
return &ErrEmailAlreadyVerified{"email already verified"}
|
|
}
|
|
|
|
func (e ErrEmailAlreadyVerified) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
type ErrIdentityNotFound struct{ IdentityID gid.GID }
|
|
|
|
func NewIdentityNotFoundError(identityID gid.GID) error {
|
|
return &ErrIdentityNotFound{identityID}
|
|
}
|
|
|
|
func (e ErrIdentityNotFound) Error() string {
|
|
return fmt.Sprintf("identity %q not found", e.IdentityID)
|
|
}
|
|
|
|
type ErrInvalidPassword struct{ message string }
|
|
|
|
func NewInvalidPasswordError(message string) error {
|
|
return &ErrInvalidPassword{message}
|
|
}
|
|
|
|
func (e ErrInvalidPassword) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
type ErrEmailVerificationMismatch struct{ message string }
|
|
|
|
func NewEmailVerificationMismatchError() error {
|
|
return &ErrEmailVerificationMismatch{"email verification mismatch"}
|
|
}
|
|
|
|
func (e ErrEmailVerificationMismatch) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
type ErrMembershipNotFound struct {
|
|
MembershipID gid.GID
|
|
}
|
|
|
|
func NewMembershipNotFoundError(membershipID gid.GID) error {
|
|
return &ErrMembershipNotFound{MembershipID: membershipID}
|
|
}
|
|
|
|
func (e ErrMembershipNotFound) Error() string {
|
|
return fmt.Sprintf("membership %q not found", e.MembershipID)
|
|
}
|
|
|
|
type ErrOrganizationNotFound struct{ OrganizationID gid.GID }
|
|
|
|
func NewOrganizationNotFoundError(organizationID gid.GID) error {
|
|
return &ErrOrganizationNotFound{OrganizationID: organizationID}
|
|
}
|
|
|
|
func (e ErrOrganizationNotFound) Error() string {
|
|
return fmt.Sprintf("organization %q not found", e.OrganizationID)
|
|
}
|
|
|
|
type ErrInsufficientPermissions struct {
|
|
IdentityID gid.GID
|
|
EntityID gid.GID
|
|
Action Action
|
|
}
|
|
|
|
func NewInsufficientPermissionsError(identityID gid.GID, entityID gid.GID, action Action) error {
|
|
return &ErrInsufficientPermissions{IdentityID: identityID, EntityID: entityID, Action: action}
|
|
}
|
|
|
|
func (e ErrInsufficientPermissions) Error() string {
|
|
return fmt.Sprintf("identity %q does not have sufficient permissions to perform action %s on entity %q", e.IdentityID, e.Action, e.EntityID)
|
|
}
|
|
|
|
type ErrSessionNotFound struct{ SessionID gid.GID }
|
|
|
|
func NewSessionNotFoundError(sessionID gid.GID) error {
|
|
return &ErrSessionNotFound{SessionID: sessionID}
|
|
}
|
|
|
|
func (e ErrSessionNotFound) Error() string {
|
|
return fmt.Sprintf("session %q not found", e.SessionID)
|
|
}
|
|
|
|
type ErrSessionExpired struct{ SessionID gid.GID }
|
|
|
|
func NewSessionExpiredError(sessionID gid.GID) error {
|
|
return &ErrSessionExpired{SessionID: sessionID}
|
|
}
|
|
|
|
func (e ErrSessionExpired) Error() string {
|
|
return fmt.Sprintf("session %q expired", e.SessionID)
|
|
}
|
|
|
|
type ErrMembershipAlreadyExists struct {
|
|
IdentityID gid.GID
|
|
OrganizationID gid.GID
|
|
}
|
|
|
|
func NewMembershipAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
|
return &ErrMembershipAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
|
}
|
|
|
|
func (e ErrMembershipAlreadyExists) Error() string {
|
|
return fmt.Sprintf("membership already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
|
}
|
|
|
|
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
|
|
|
|
func NewSAMLConfigurationNotFoundError(configID gid.GID) error {
|
|
return &ErrSAMLConfigurationNotFound{ConfigID: configID}
|
|
}
|
|
|
|
func (e ErrSAMLConfigurationNotFound) Error() string {
|
|
return fmt.Sprintf("SAML configuration %q not found", e.ConfigID)
|
|
}
|
|
|
|
type ErrPersonalAPIKeyNotFound struct{ PersonalAPIKeyID gid.GID }
|
|
|
|
func NewPersonalAPIKeyNotFoundError(personalAPIKeyID gid.GID) error {
|
|
return &ErrPersonalAPIKeyNotFound{PersonalAPIKeyID: personalAPIKeyID}
|
|
}
|
|
|
|
func (e ErrPersonalAPIKeyNotFound) Error() string {
|
|
return fmt.Sprintf("personal API key %q not found", e.PersonalAPIKeyID)
|
|
}
|
|
|
|
type ErrProfileNotFound struct{ MembershipID gid.GID }
|
|
|
|
func NewProfileNotFoundError(membershipID gid.GID) error {
|
|
return &ErrProfileNotFound{MembershipID: membershipID}
|
|
}
|
|
|
|
func (e ErrProfileNotFound) Error() string {
|
|
return fmt.Sprintf("profile for membership %q not found", e.MembershipID)
|
|
}
|
|
|
|
type ErrPersonalAPIKeyExpired struct{ PersonalAPIKeyID gid.GID }
|
|
|
|
func NewPersonalAPIKeyExpiredError(personalAPIKeyID gid.GID) error {
|
|
return &ErrPersonalAPIKeyExpired{PersonalAPIKeyID: personalAPIKeyID}
|
|
}
|
|
|
|
func (e ErrPersonalAPIKeyExpired) Error() string {
|
|
return fmt.Sprintf("personal API key %q expired", e.PersonalAPIKeyID)
|
|
}
|
|
|
|
type ErrSAMLConfigurationDomainNotVerified struct{ ConfigID gid.GID }
|
|
|
|
func NewSAMLConfigurationDomainNotVerifiedError(configID gid.GID) error {
|
|
return &ErrSAMLConfigurationDomainNotVerified{ConfigID: configID}
|
|
}
|
|
|
|
func (e ErrSAMLConfigurationDomainNotVerified) Error() string {
|
|
return fmt.Sprintf("SAML configuration %q domain not verified", e.ConfigID)
|
|
}
|
|
|
|
type ErrUnsupportedPrincipalType struct{ EntityType uint16 }
|
|
|
|
func NewUnsupportedPrincipalTypeError(entityType uint16) error {
|
|
return &ErrUnsupportedPrincipalType{EntityType: entityType}
|
|
}
|
|
|
|
func (e ErrUnsupportedPrincipalType) Error() string {
|
|
return fmt.Sprintf("unsupported principal type: %d", e.EntityType)
|
|
}
|
|
|
|
type ErrNoPermissionsDefined struct {
|
|
EntityModel string
|
|
Action Action
|
|
}
|
|
|
|
func NewNoPermissionsDefinedError(entityModel string, action Action) error {
|
|
return &ErrNoPermissionsDefined{EntityModel: entityModel, Action: action}
|
|
}
|
|
|
|
func (e ErrNoPermissionsDefined) Error() string {
|
|
return fmt.Sprintf("no permissions defined for action %s on entity %s", e.Action, e.EntityModel)
|
|
}
|
|
|
|
type ErrSignupDisabled struct{}
|
|
|
|
func NewErrSignupDisabled() error {
|
|
return &ErrSignupDisabled{}
|
|
}
|
|
|
|
func (e ErrSignupDisabled) Error() string {
|
|
return "signup is disabled"
|
|
}
|
|
|
|
type ErrInvalidCredentials struct{ message string }
|
|
|
|
func NewInvalidCredentialsError(message string) error {
|
|
return &ErrInvalidCredentials{message}
|
|
}
|
|
|
|
func (e ErrInvalidCredentials) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
type ErrInvitationNotPending struct{ InvitationID gid.GID }
|
|
|
|
func NewInvitationNotPendingError(invitationID gid.GID) error {
|
|
return &ErrInvitationNotPending{InvitationID: invitationID}
|
|
}
|
|
|
|
func (e ErrInvitationNotPending) Error() string {
|
|
return fmt.Sprintf("invitation %q is not pending", e.InvitationID)
|
|
}
|
|
|
|
type ErrPasswordRequired struct {
|
|
Reason string
|
|
}
|
|
|
|
func NewPasswordRequiredError(reason string) *ErrPasswordRequired {
|
|
return &ErrPasswordRequired{Reason: reason}
|
|
}
|
|
|
|
func (e *ErrPasswordRequired) Error() string {
|
|
return fmt.Sprintf("password authentication required: %s", e.Reason)
|
|
}
|
|
|
|
type ErrSAMLAuthenticationRequired struct {
|
|
Reason string
|
|
RedirectURL string
|
|
}
|
|
|
|
func NewSAMLAuthenticationRequiredError(reason string, redirectURL string) *ErrSAMLAuthenticationRequired {
|
|
return &ErrSAMLAuthenticationRequired{Reason: reason, RedirectURL: redirectURL}
|
|
}
|
|
|
|
func (e *ErrSAMLAuthenticationRequired) Error() string {
|
|
return fmt.Sprintf("SAML authentication required: %s", e.Reason)
|
|
}
|
|
|
|
// TenantAccessError is used by API recovery middleware to translate authorization/tenant failures
|
|
// into a consistent client-facing error response.
|
|
//
|
|
// NOTE: This is intentionally generic to avoid leaking resource existence.
|
|
type TenantAccessError struct {
|
|
Message string
|
|
}
|
|
|
|
func (e *TenantAccessError) Error() string {
|
|
if e == nil || e.Message == "" {
|
|
return "tenant access denied"
|
|
}
|
|
return e.Message
|
|
}
|