yao/openapi/oauth/process.go
Max 4d22a9a655 feat(user): add token-based login endpoint for automation and testing
- Implemented GinTokenLogin function to handle POST requests for token-based login.
- Allows users to authenticate using a pre-signed access token, returning session cookies and user info.
- Updated user routes to include the new /token/login endpoint for public access.
2026-04-22 14:12:53 +08:00

126 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package oauth
import (
"context"
"fmt"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/kun/exception"
)
func init() {
process.RegisterGroup("oauth", map[string]process.Handler{
"token.Make": processTokenMake,
"token.MakeByUser": processTokenMakeByUser,
})
}
// processTokenMake generates an OAuth access token with explicit parameters.
//
// Args:
//
// [0] clientID string OAuth client ID embedded in the token
// [1] scope string token scope (space-separated)
// [2] subject string JWT subject claim
// [3] expiresIn int token lifetime in seconds
// [4] extraClaims map (optional) additional JWT claims (e.g. user_id, team_id)
//
// Returns: token string
//
// Example: Process("oauth.token.Make", "tai-agent-smith", "tai:tunnel", "ci-tai", 86400)
func processTokenMake(p *process.Process) interface{} {
p.ValidateArgNums(4)
clientID := p.ArgsString(0)
scope := p.ArgsString(1)
subject := p.ArgsString(2)
expiresIn := p.ArgsInt(3)
if OAuth == nil {
exception.New("oauth service not initialized", 500).Throw()
}
var extraClaims map[string]interface{}
if p.NumOfArgs() > 4 {
if claims, ok := p.Args[4].(map[string]interface{}); ok {
extraClaims = claims
}
}
token, err := OAuth.MakeAccessToken(clientID, scope, subject, expiresIn, extraClaims)
if err != nil {
exception.New(fmt.Sprintf("oauth.token.Make: %v", err), 500).Throw()
}
return token
}
// processTokenMakeByUser generates an OAuth access token for a team member.
// Looks up user and team from the database, automatically filling clientID, scope, subject, and claims.
//
// Args:
//
// [0] teamID string team ID
// [1] memberID string member ID (business ID)
// [2] expiresIn int token lifetime in seconds (optional, default 86400 = 24h)
//
// Returns: token string
//
// Example: Process("oauth.token.MakeByUser", "team-abc", "member-xyz")
// Example: Process("oauth.token.MakeByUser", "team-abc", "member-xyz", 3600)
func processTokenMakeByUser(p *process.Process) interface{} {
p.ValidateArgNums(2)
teamID := p.ArgsString(0)
memberID := p.ArgsString(1)
expiresIn := 86400 // default 24h
if p.NumOfArgs() > 2 {
if v := p.ArgsInt(2); v > 0 {
expiresIn = v
}
}
if OAuth == nil {
exception.New("oauth service not initialized", 500).Throw()
}
userProvider, err := OAuth.GetUserProvider()
if err != nil {
exception.New(fmt.Sprintf("oauth.token.MakeByUser: failed to get user provider: %v", err), 500).Throw()
}
ctx := context.Background()
member, err := userProvider.GetMemberByMemberID(ctx, memberID)
if err != nil {
exception.New(fmt.Sprintf("oauth.token.MakeByUser: member not found: %v", err), 404).Throw()
}
memberTeamID := ""
if v, ok := member["team_id"].(string); ok {
memberTeamID = v
}
if memberTeamID != teamID {
exception.New(fmt.Sprintf("oauth.token.MakeByUser: member %s does not belong to team %s", memberID, teamID), 403).Throw()
}
userID := ""
if v, ok := member["user_id"].(string); ok {
userID = v
}
if userID == "" {
exception.New(fmt.Sprintf("oauth.token.MakeByUser: member %s has no user_id", memberID), 500).Throw()
}
subject := userID
extraClaims := map[string]interface{}{
"user_id": userID,
"team_id": teamID,
"member_id": memberID,
}
token, err := OAuth.MakeAccessToken("yao-admin", "openid profile", subject, expiresIn, extraClaims)
if err != nil {
exception.New(fmt.Sprintf("oauth.token.MakeByUser: %v", err), 500).Throw()
}
return token
}