feat: interactive auth method selection with API token validation
- Add GetToken() as unified auth entry point with method selection - Prompt [1] OAuth / [2] API token on first launch, save choice - Validate API tokens against /api/v1/users/self before saving - Check saved API token validity on reuse; fallback to re-prompt on failure - Add HTTP status check to GetCourseInfo and GetEnrolledCourses - Remove --api flag, auth method is now persistent and interactive
This commit is contained in:
@@ -27,7 +27,32 @@ func NewAuthenticator(client *http.Client) *Authenticator {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Authenticator) GetAccessToken() (string, error) {
|
||||
func (a *Authenticator) GetToken() (string, error) {
|
||||
creds, err := LoadCredentials()
|
||||
if err == nil && creds.AuthMethod != "" {
|
||||
switch creds.AuthMethod {
|
||||
case "api":
|
||||
token := strings.TrimSpace(creds.APIToken)
|
||||
if token == "" {
|
||||
return a.promptMethod()
|
||||
}
|
||||
fmt.Print("[*] Validating saved API token... ")
|
||||
if err := a.validateAPIToken(token); err != nil {
|
||||
fmt.Println("FAILED")
|
||||
fmt.Printf("[!] %v\n", err)
|
||||
SaveCredentials(&models.Credentials{})
|
||||
return a.promptMethod()
|
||||
}
|
||||
fmt.Println("OK")
|
||||
return token, nil
|
||||
case "oauth":
|
||||
return a.refreshOrLogin()
|
||||
}
|
||||
}
|
||||
return a.promptMethod()
|
||||
}
|
||||
|
||||
func (a *Authenticator) refreshOrLogin() (string, error) {
|
||||
creds, err := LoadCredentials()
|
||||
if err != nil {
|
||||
return a.login()
|
||||
@@ -52,36 +77,66 @@ func (a *Authenticator) GetAccessToken() (string, error) {
|
||||
return tr.AccessToken, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) GetAPIToken() (string, error) {
|
||||
creds, err := LoadCredentials()
|
||||
if err == nil && strings.TrimSpace(creds.APIToken) != "" {
|
||||
fmt.Println("[*] Reusing saved API token...")
|
||||
return strings.TrimSpace(creds.APIToken), nil
|
||||
func (a *Authenticator) promptMethod() (string, error) {
|
||||
fmt.Println("Select authentication method:")
|
||||
fmt.Println(" [1] Login via browser (OAuth)")
|
||||
fmt.Println(" [2] Use Canvas API token")
|
||||
fmt.Print("Choice (1/2): ")
|
||||
input, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
if err != nil {
|
||||
creds = &models.Credentials{}
|
||||
switch strings.TrimSpace(input) {
|
||||
case "2":
|
||||
return a.setupAPIToken()
|
||||
default:
|
||||
return a.login()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Authenticator) setupAPIToken() (string, error) {
|
||||
fmt.Print("Enter Canvas API token: ")
|
||||
token, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return "", fmt.Errorf("empty API token")
|
||||
}
|
||||
|
||||
creds.APIToken = token
|
||||
if err := SaveCredentials(creds); err != nil {
|
||||
fmt.Print("[*] Validating API token... ")
|
||||
if err := a.validateAPIToken(token); err != nil {
|
||||
fmt.Println("FAILED")
|
||||
return "", err
|
||||
}
|
||||
fmt.Println("OK")
|
||||
|
||||
if err := SaveCredentials(&models.Credentials{
|
||||
AuthMethod: "api",
|
||||
APIToken: token,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Println("[+] API token saved.")
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) validateAPIToken(token string) error {
|
||||
req, _ := http.NewRequest("GET", config.BaseURL+"/api/v1/users/self", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err := a.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("invalid token: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) login() (string, error) {
|
||||
codeVerifier, codeChallenge, err := generatePKCEPair()
|
||||
if err != nil {
|
||||
@@ -211,6 +266,7 @@ func SaveRefreshToken(refreshToken string) error {
|
||||
creds = &models.Credentials{}
|
||||
}
|
||||
creds.RefreshToken = refreshToken
|
||||
creds.AuthMethod = "oauth"
|
||||
return SaveCredentials(creds)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user