- Removed the previous MFA check from the LoginThirdParty function and integrated MFA token generation within the LoginByUserID function. - Enhanced the LoginResponse structure to include MFAToken and MFATokenExpiresIn fields for better MFA handling. - Updated the SendLoginCookies function to manage MFA token cookies appropriately. - Adjusted the authback function to respond with MFA status and token when MFA is required, improving the login flow for users with MFA enabled.
416 lines
18 KiB
Go
416 lines
18 KiB
Go
package user
|
|
|
|
import (
|
|
oauthtypes "github.com/yaoapp/yao/openapi/oauth/types"
|
|
)
|
|
|
|
// LoginStatus represents the login status
|
|
type LoginStatus string
|
|
|
|
const (
|
|
// LoginStatusSuccess is the success status
|
|
LoginStatusSuccess LoginStatus = "ok"
|
|
// LoginStatusMFA is the MFA status
|
|
LoginStatusMFA LoginStatus = "mfa_required"
|
|
// LoginStatusTeamSelection is the team selection status
|
|
LoginStatusTeamSelection LoginStatus = "team_selection_required"
|
|
)
|
|
|
|
const (
|
|
// ScopeMFAVerification is the MFA verification scope for temporary access token
|
|
ScopeMFAVerification = "mfa_verification"
|
|
)
|
|
|
|
// Config represents the signin page configuration
|
|
type Config struct {
|
|
Title string `json:"title,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Default bool `json:"default,omitempty"`
|
|
SuccessURL string `json:"success_url,omitempty"`
|
|
FailureURL string `json:"failure_url,omitempty"`
|
|
ClientID string `json:"client_id,omitempty"`
|
|
ClientSecret string `json:"client_secret,omitempty"`
|
|
Form *FormConfig `json:"form,omitempty"`
|
|
Token *TokenConfig `json:"token,omitempty"`
|
|
ThirdParty *ThirdParty `json:"third_party,omitempty"`
|
|
}
|
|
|
|
// FormConfig represents the form configuration
|
|
type FormConfig struct {
|
|
Username *UsernameConfig `json:"username,omitempty"`
|
|
Password *PasswordConfig `json:"password,omitempty"`
|
|
Captcha *CaptchaConfig `json:"captcha,omitempty"`
|
|
ForgotPasswordLink bool `json:"forgot_password_link,omitempty"`
|
|
RememberMe bool `json:"remember_me,omitempty"`
|
|
RegisterLink string `json:"register_link,omitempty"`
|
|
TermsOfServiceLink string `json:"terms_of_service_link,omitempty"`
|
|
PrivacyPolicyLink string `json:"privacy_policy_link,omitempty"`
|
|
}
|
|
|
|
// UsernameConfig represents the username field configuration
|
|
type UsernameConfig struct {
|
|
Placeholder string `json:"placeholder,omitempty"`
|
|
Fields []string `json:"fields,omitempty"`
|
|
}
|
|
|
|
// PasswordConfig represents the password field configuration
|
|
type PasswordConfig struct {
|
|
Placeholder string `json:"placeholder,omitempty"`
|
|
}
|
|
|
|
// CaptchaConfig represents the captcha configuration
|
|
type CaptchaConfig struct {
|
|
Type string `json:"type,omitempty"`
|
|
Options map[string]interface{} `json:"options,omitempty"`
|
|
}
|
|
|
|
// TokenConfig represents the token configuration
|
|
type TokenConfig struct {
|
|
ExpiresIn string `json:"expires_in,omitempty"`
|
|
RememberMeExpiresIn string `json:"remember_me_expires_in,omitempty"`
|
|
}
|
|
|
|
// ThirdParty represents the third party login configuration
|
|
type ThirdParty struct {
|
|
Providers []*Provider `json:"providers,omitempty"`
|
|
}
|
|
|
|
// RegisterConfig represents the auto register configuration
|
|
type RegisterConfig struct {
|
|
Auto bool `json:"auto,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
}
|
|
|
|
// YaoClientConfig represents the Yao OpenAPI Client config
|
|
type YaoClientConfig struct {
|
|
ClientID string `json:"client_id,omitempty"`
|
|
ClientSecret string `json:"client_secret,omitempty"`
|
|
Scopes []string `json:"scopes,omitempty"` // Default scopes if not set in the provider config
|
|
ExpiresIn int `json:"expires_in,omitempty"` // Default expires in for the access token (optional) in seconds
|
|
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"` // Default expires in for the refresh token (optional) in seconds
|
|
}
|
|
|
|
// Provider represents a third party login provider
|
|
type Provider struct {
|
|
ID string `json:"id,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
Title string `json:"title,omitempty"`
|
|
Logo string `json:"logo,omitempty"`
|
|
Color string `json:"color,omitempty"`
|
|
TextColor string `json:"text_color,omitempty"`
|
|
ClientID string `json:"client_id,omitempty"`
|
|
ClientSecret string `json:"client_secret,omitempty"`
|
|
ClientSecretGenerator *SecretGenerator `json:"client_secret_generator,omitempty"`
|
|
Scopes []string `json:"scopes,omitempty"`
|
|
ResponseMode string `json:"response_mode,omitempty"`
|
|
UserInfoSource string `json:"user_info_source,omitempty"` // "endpoint" (default) | "id_token" | "access_token"
|
|
Endpoints *Endpoints `json:"endpoints,omitempty"`
|
|
Mapping interface{} `json:"mapping,omitempty"` // string (preset) | map[string]string (custom) | nil (generic)
|
|
Register *RegisterConfig `json:"register,omitempty"`
|
|
}
|
|
|
|
// SecretGenerator represents the client secret generator configuration
|
|
type SecretGenerator struct {
|
|
Type string `json:"type,omitempty"`
|
|
ExpiresIn string `json:"expires_in,omitempty"`
|
|
PrivateKey string `json:"private_key,omitempty"`
|
|
Header map[string]interface{} `json:"header,omitempty"`
|
|
Payload map[string]interface{} `json:"payload,omitempty"`
|
|
}
|
|
|
|
// Endpoints represents the OAuth endpoints
|
|
type Endpoints struct {
|
|
Authorization string `json:"authorization,omitempty"`
|
|
Token string `json:"token,omitempty"`
|
|
UserInfo string `json:"user_info,omitempty"`
|
|
JWKS string `json:"jwks,omitempty"` // JSON Web Key Set endpoint for token verification
|
|
}
|
|
|
|
// ==== API Types ====
|
|
|
|
// OAuthAuthorizationURLResponse represents the response for OAuth authorization URL
|
|
type OAuthAuthorizationURLResponse struct {
|
|
AuthorizationURL string `json:"authorization_url"`
|
|
State string `json:"state"`
|
|
Warnings []string `json:"warnings,omitempty"` // Optional warnings about state format or other issues
|
|
}
|
|
|
|
// OAuthCallbackResponse represents the response for OAuth callback
|
|
type OAuthCallbackResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
}
|
|
|
|
// OAuthAuthbackRequest represents the request for OAuth callback
|
|
type OAuthAuthbackRequest struct {
|
|
Locale string `json:"locale" form:"locale"`
|
|
Code string `json:"code" form:"code"`
|
|
State string `json:"state" form:"state"`
|
|
Provider string `json:"provider" form:"provider"`
|
|
Scope string `json:"scope,omitempty" form:"scope,omitempty"`
|
|
}
|
|
|
|
// OAuthTokenResponse represents the response from OAuth token endpoint
|
|
type OAuthTokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
Scope string `json:"scope"`
|
|
IDToken string `json:"id_token,omitempty"` // JWT token containing user info (Apple, etc.)
|
|
Error string `json:"error"`
|
|
ErrorDesc string `json:"error_description"`
|
|
}
|
|
|
|
// OAuthTokenRequest represents the request to OAuth token endpoint
|
|
type OAuthTokenRequest struct {
|
|
GrantType string `json:"grant_type" form:"grant_type"`
|
|
Code string `json:"code" form:"code"`
|
|
ClientID string `json:"client_id" form:"client_id"`
|
|
ClientSecret string `json:"client_secret" form:"client_secret"`
|
|
RedirectURI string `json:"redirect_uri,omitempty" form:"redirect_uri,omitempty"`
|
|
}
|
|
|
|
// OAuthUserInfoResponse is an alias for OIDC standard user information type
|
|
type OAuthUserInfoResponse = oauthtypes.OIDCUserInfo
|
|
|
|
// OIDCAddress is an alias for OIDC standard address claim type
|
|
type OIDCAddress = oauthtypes.OIDCAddress
|
|
|
|
// LoginResponse represents the response for login
|
|
type LoginResponse struct {
|
|
UserID string `json:"user_id,omitempty"`
|
|
Subject string `json:"subject,omitempty"`
|
|
AccessToken string `json:"access_token"`
|
|
IDToken string `json:"id_token,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
ExpiresIn int `json:"expires_in,omitempty"`
|
|
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"`
|
|
TokenType string `json:"token_type,omitempty"`
|
|
MFAToken string `json:"mfa_token,omitempty"` // MFA token verification code
|
|
MFATokenExpiresIn int `json:"mfa_token_expires_in,omitempty"` // MFA token verification code expires in
|
|
MFAEnabled bool `json:"mfa_enabled,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
Status LoginStatus `json:"status,omitempty"`
|
|
}
|
|
|
|
// LoginSuccessResponse represents the response for login success
|
|
type LoginSuccessResponse struct {
|
|
IDToken string `json:"id_token,omitempty"`
|
|
AccessToken string `json:"access_token,omitempty"`
|
|
MFAToken string `json:"mfa_token,omitempty"` // MFA token verification code
|
|
MFATokenExpiresIn int `json:"mfa_token_expires_in,omitempty"` // MFA token verification code expires in
|
|
SessionID string `json:"session_id,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
ExpiresIn int `json:"expires_in,omitempty"`
|
|
MFAEnabled bool `json:"mfa_enabled"`
|
|
RefreshTokenExpiresIn int `json:"refresh_token_expires_in,omitempty"`
|
|
Status LoginStatus `json:"status,omitempty"`
|
|
}
|
|
|
|
// Built-in preset mapping types
|
|
const (
|
|
MappingGoogle = "google"
|
|
MappingGitHub = "github"
|
|
MappingMicrosoft = "microsoft"
|
|
MappingApple = "apple"
|
|
MappingWeChat = "wechat"
|
|
MappingGeneric = "generic"
|
|
)
|
|
|
|
// User info source types
|
|
const (
|
|
UserInfoSourceEndpoint = "endpoint" // Default: Get user info from dedicated endpoint
|
|
UserInfoSourceIDToken = "id_token" // Extract user info from ID token (JWT)
|
|
UserInfoSourceAccessToken = "access_token" // Extract user info from access token response
|
|
)
|
|
|
|
// ==== Settings Types ====
|
|
|
|
// TeamSettings represents team-specific settings
|
|
type TeamSettings struct {
|
|
Theme string `json:"theme,omitempty"` // Team UI theme (e.g., "light", "dark")
|
|
Visibility string `json:"visibility,omitempty"` // Team visibility (e.g., "public", "private")
|
|
}
|
|
|
|
// MemberSettings represents member-specific settings
|
|
type MemberSettings struct {
|
|
Notifications bool `json:"notifications,omitempty"` // Whether to receive notifications
|
|
Permissions []string `json:"permissions,omitempty"` // Custom permissions (e.g., ["read", "write"])
|
|
}
|
|
|
|
// InvitationSettings represents invitation-specific settings
|
|
type InvitationSettings struct {
|
|
SendEmail bool `json:"send_email,omitempty"` // Whether to send invitation email
|
|
Locale string `json:"locale,omitempty"` // Locale for email template
|
|
}
|
|
|
|
// ==== Team API Types ====
|
|
|
|
// TeamResponse represents a team in API responses
|
|
type TeamResponse struct {
|
|
ID int64 `json:"id"`
|
|
TeamID string `json:"team_id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
OwnerID string `json:"owner_id"`
|
|
Status string `json:"status"`
|
|
IsVerified bool `json:"is_verified"`
|
|
VerifiedBy string `json:"verified_by,omitempty"`
|
|
VerifiedAt string `json:"verified_at,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// TeamDetailResponse represents detailed team information
|
|
type TeamDetailResponse struct {
|
|
TeamResponse
|
|
// Add additional fields that are only included in detailed responses
|
|
Settings *TeamSettings `json:"settings,omitempty"`
|
|
}
|
|
|
|
// CreateTeamRequest represents the request to create a team
|
|
type CreateTeamRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
Settings *TeamSettings `json:"settings,omitempty"`
|
|
}
|
|
|
|
// UpdateTeamRequest represents the request to update a team
|
|
type UpdateTeamRequest struct {
|
|
Name string `json:"name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Settings *TeamSettings `json:"settings,omitempty"`
|
|
}
|
|
|
|
// ==== Member API Types ====
|
|
|
|
// MemberResponse represents a team member in API responses
|
|
type MemberResponse struct {
|
|
ID int64 `json:"id"`
|
|
TeamID string `json:"team_id"`
|
|
UserID string `json:"user_id"`
|
|
MemberType string `json:"member_type"`
|
|
RoleID string `json:"role_id"`
|
|
Status string `json:"status"`
|
|
InvitedBy string `json:"invited_by,omitempty"`
|
|
InvitedAt string `json:"invited_at,omitempty"`
|
|
JoinedAt string `json:"joined_at,omitempty"`
|
|
LastActivity string `json:"last_activity,omitempty"`
|
|
Settings *MemberSettings `json:"settings,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// MemberDetailResponse represents detailed member information
|
|
type MemberDetailResponse struct {
|
|
MemberResponse
|
|
// Add additional fields that are only included in detailed responses
|
|
UserInfo map[string]interface{} `json:"user_info,omitempty"`
|
|
}
|
|
|
|
// CreateMemberRequest represents the request to add a member directly
|
|
type CreateMemberRequest struct {
|
|
UserID string `json:"user_id" binding:"required"`
|
|
MemberType string `json:"member_type,omitempty"` // "user" or "robot"
|
|
RoleID string `json:"role_id" binding:"required"`
|
|
Settings *MemberSettings `json:"settings,omitempty"`
|
|
}
|
|
|
|
// UpdateMemberRequest represents the request to update a member
|
|
type UpdateMemberRequest struct {
|
|
RoleID string `json:"role_id,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
Settings *MemberSettings `json:"settings,omitempty"`
|
|
LastActivity string `json:"last_activity,omitempty"`
|
|
}
|
|
|
|
// ==== Invitation API Types ====
|
|
|
|
// InvitationResponse represents a team invitation in API responses
|
|
type InvitationResponse struct {
|
|
ID int64 `json:"id"`
|
|
InvitationID string `json:"invitation_id"`
|
|
TeamID string `json:"team_id"`
|
|
UserID string `json:"user_id"`
|
|
MemberType string `json:"member_type"`
|
|
RoleID string `json:"role_id"`
|
|
Status string `json:"status"`
|
|
InvitedBy string `json:"invited_by"`
|
|
InvitedAt string `json:"invited_at"`
|
|
InvitationToken string `json:"invitation_token,omitempty"`
|
|
InvitationLink string `json:"invitation_link,omitempty"` // Full invitation link
|
|
InvitationExpiresAt string `json:"invitation_expires_at,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Settings *InvitationSettings `json:"settings,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// InvitationDetailResponse represents detailed invitation information
|
|
type InvitationDetailResponse struct {
|
|
InvitationResponse
|
|
// Add additional fields that are only included in detailed responses
|
|
UserInfo map[string]interface{} `json:"user_info,omitempty"`
|
|
TeamInfo map[string]interface{} `json:"team_info,omitempty"`
|
|
}
|
|
|
|
// PublicInvitationResponse represents a public team invitation (for invitation recipients)
|
|
// This type excludes sensitive information like tokens, database IDs, and timestamps
|
|
type PublicInvitationResponse struct {
|
|
InvitationID string `json:"invitation_id"`
|
|
TeamName string `json:"team_name"`
|
|
TeamLogo string `json:"team_logo"` // Always return, empty string if not set
|
|
TeamDescription string `json:"team_description"` // Always return, empty string if not set
|
|
RoleLabel string `json:"role_label,omitempty"`
|
|
Status string `json:"status"`
|
|
InvitedAt string `json:"invited_at"`
|
|
InvitationExpiresAt string `json:"invitation_expires_at,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
InviterInfo *InviterInfo `json:"inviter_info,omitempty"` // Inviter's public info
|
|
}
|
|
|
|
// InviterInfo represents public information about the person who sent the invitation
|
|
type InviterInfo struct {
|
|
Name string `json:"name,omitempty"`
|
|
Picture string `json:"picture"` // Always return, empty string if not set
|
|
}
|
|
|
|
// CreateInvitationRequest represents the request to send a team invitation
|
|
type CreateInvitationRequest struct {
|
|
UserID string `json:"user_id,omitempty"` // Optional for unregistered users
|
|
Email string `json:"email,omitempty"` // Email address (if not provided, will be read from user profile when user_id is provided)
|
|
MemberType string `json:"member_type,omitempty"` // "user" or "robot"
|
|
RoleID string `json:"role_id" binding:"required"`
|
|
Message string `json:"message,omitempty"`
|
|
Expiry string `json:"expiry,omitempty"` // Custom expiry duration (e.g., "1d", "8h"), defaults to team config
|
|
SendEmail *bool `json:"send_email,omitempty"` // Whether to send email (defaults to false)
|
|
Locale string `json:"locale,omitempty"` // Language code for email template (e.g., "zh-CN", "en")
|
|
Settings *InvitationSettings `json:"settings,omitempty"`
|
|
}
|
|
|
|
// ==== Team Configuration Types ====
|
|
|
|
// TeamConfig represents the team configuration loaded from DSL files
|
|
type TeamConfig struct {
|
|
Roles []*TeamRole `json:"roles,omitempty"`
|
|
Invite *InviteConfig `json:"invite,omitempty"`
|
|
}
|
|
|
|
// TeamRole represents a team role configuration
|
|
type TeamRole struct {
|
|
RoleID string `json:"role_id"`
|
|
Label string `json:"label"`
|
|
Description string `json:"description"`
|
|
Default bool `json:"default"` // Whether this role is the default role
|
|
Hidden bool `json:"hidden"` // Whether this role is hidden from UI
|
|
}
|
|
|
|
// InviteConfig represents the invitation configuration
|
|
type InviteConfig struct {
|
|
Channel string `json:"channel,omitempty"`
|
|
Expiry string `json:"expiry,omitempty"`
|
|
BaseURL string `json:"base_url,omitempty"` // Base URL for invitation links
|
|
Templates map[string]string `json:"templates,omitempty"`
|
|
}
|