diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d5fa170..181a558a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ # Change Log +## 2025-04-01 - Runtime 0.17.8 + +- fix: Update Hypermode api key reading for modus local dev [#805](https://github.com/hypermodeinc/modus/pull/805) + +## 2025-04-01 - CLI 0.17.2 + +- fix: Update Hypermode api key reading for modus local dev [#805](https://github.com/hypermodeinc/modus/pull/805) + ## 2025-03-28 - Runtime 0.17.7 - feat: support new Dgraph connection string format [#803](https://github.com/hypermodeinc/modus/pull/803) diff --git a/cli/src/commands/dev/index.ts b/cli/src/commands/dev/index.ts index 558264130..55dd56110 100644 --- a/cli/src/commands/dev/index.ts +++ b/cli/src/commands/dev/index.ts @@ -164,8 +164,8 @@ export default class DevCommand extends BaseCommand { ...process.env, MODUS_ENV: "dev", HYP_EMAIL: hypSettings.email, - HYP_JWT: hypSettings.jwt, - HYP_ORG_ID: hypSettings.orgId, + HYP_API_KEY: hypSettings.apiKey, + HYP_WORKSPACE_ID: hypSettings.workspaceId, }; // Spawn the runtime child process diff --git a/cli/src/util/hypermode.ts b/cli/src/util/hypermode.ts index 742237fac..1fbf6bfff 100644 --- a/cli/src/util/hypermode.ts +++ b/cli/src/util/hypermode.ts @@ -14,8 +14,8 @@ import chalk from "chalk"; type HypSettings = { email?: string; - jwt?: string; - orgId?: string; + apiKey?: string; + workspaceId?: string; }; export async function readHypermodeSettings(): Promise { @@ -28,8 +28,8 @@ export async function readHypermodeSettings(): Promise { const settings = JSON.parse(await fs.readFile(path, "utf-8")); return { email: settings.HYP_EMAIL, - jwt: settings.HYP_JWT, - orgId: settings.HYP_ORG_ID, + apiKey: settings.HYP_API_KEY, + workspaceId: settings.HYP_WORKSPACE_ID, }; } catch (e) { console.warn(chalk.yellow("Error reading " + path), e); diff --git a/runtime/models/models.go b/runtime/models/models.go index a28bb7985..c7825ec1e 100644 --- a/runtime/models/models.go +++ b/runtime/models/models.go @@ -93,8 +93,17 @@ func PostToModelEndpoint[TResult any](ctx context.Context, model *manifest.Model var empty TResult var httpe *utils.HttpError if errors.As(err, &httpe) { - if app.IsDevEnvironment() && httpe.StatusCode == http.StatusNotFound { - return empty, fmt.Errorf("model %s is not available in the local dev environment", model.SourceModel) + switch httpe.StatusCode { + case http.StatusNotFound: + if app.IsDevEnvironment() { + return empty, fmt.Errorf("model %s is not available in the local dev environment", model.SourceModel) + } + case http.StatusUnauthorized: + return empty, fmt.Errorf("invalid or expired API key. Please use `hyp login` to create a new API key") + case http.StatusForbidden: + return empty, fmt.Errorf("API key is disabled or usage limit exceeded") + case http.StatusTooManyRequests: + return empty, fmt.Errorf("rate limit exceeded") } } diff --git a/runtime/secrets/secrets.go b/runtime/secrets/secrets.go index 3e06b926e..672b80f32 100644 --- a/runtime/secrets/secrets.go +++ b/runtime/secrets/secrets.go @@ -16,10 +16,8 @@ import ( "net/http" "os" "regexp" - "time" "github.com/fatih/color" - "github.com/golang-jwt/jwt/v5" "github.com/hypermodeinc/modus/lib/manifest" "github.com/hypermodeinc/modus/runtime/logger" "github.com/hypermodeinc/modus/runtime/utils" @@ -94,57 +92,24 @@ func ApplySecretsToHttpRequest(ctx context.Context, connection *manifest.HTTPCon func ApplyAuthToLocalHypermodeModelRequest(ctx context.Context, connection manifest.ConnectionInfo, req *http.Request) error { - jwt := os.Getenv("HYP_JWT") - orgId := os.Getenv("HYP_ORG_ID") + apiKey := os.Getenv("HYP_API_KEY") + workspaceId := os.Getenv("HYP_WORKSPACE_ID") warningColor := color.New(color.FgHiYellow, color.Bold) - if jwt == "" || orgId == "" { + if apiKey == "" || workspaceId == "" { fmt.Fprintln(os.Stderr) warningColor.Fprintln(os.Stderr, "Warning: Local authentication not found. Please login using `hyp login`") fmt.Fprintln(os.Stderr) return errLocalAuthFailed } - isExpired, err := checkJWTExpiration(jwt) - if err != nil { - return err - } - if isExpired { - fmt.Fprintln(os.Stderr) - warningColor.Fprintln(os.Stderr, "Warning: Local authentication expired. Please login using `hyp login`") - fmt.Fprintln(os.Stderr) - return errLocalAuthFailed - } - - req.Header.Set("Authorization", "Bearer "+jwt) - req.Header.Set("HYP-ORG-ID", orgId) + req.Header.Set("Authorization", "Bearer "+apiKey) + req.Header.Set("HYP-WORKSPACE-ID", workspaceId) return nil } -// checkJWTExpiration checks if the JWT has expired based on the 'exp' claim. -func checkJWTExpiration(tokenString string) (bool, error) { - p := jwt.Parser{} - token, _, err := p.ParseUnverified(tokenString, jwt.MapClaims{}) - if err != nil { - return false, fmt.Errorf("failed to parse: %w", err) - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - return false, fmt.Errorf("failed to extract claims from JWT") - } - - exp, ok := claims["exp"].(float64) - if !ok { - return false, fmt.Errorf("exp claim is missing or not a number") - } - - expirationTime := time.Unix(int64(exp), 0) - return time.Now().After(expirationTime), nil -} - // ApplySecretsToString evaluates the given string and replaces any placeholders // present in the string with their secret values for the given connection. func ApplySecretsToString(ctx context.Context, connection manifest.ConnectionInfo, str string) (string, error) {