forge-runtime 0.9.0

Runtime executors and gateway for the Forge framework
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Authorize - {{app_name}}</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 1rem; }
  .card { background: #fff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 2rem; max-width: 400px; width: 100%; }
  h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; color: #111; }
  .subtitle { color: #666; font-size: 0.875rem; margin-bottom: 1.5rem; }
  .client-name { font-weight: 600; color: #333; }
  .field { margin-bottom: 1rem; }
  label { display: block; font-size: 0.8125rem; font-weight: 500; color: #444; margin-bottom: 0.25rem; }
  input[type="email"], input[type="password"] { width: 100%; padding: 0.625rem 0.75rem; border: 1px solid #ddd; border-radius: 8px; font-size: 0.9375rem; outline: none; transition: border-color 0.15s; }
  input:focus { border-color: #333; }
  .btn { width: 100%; padding: 0.75rem; border: none; border-radius: 8px; font-size: 0.9375rem; font-weight: 600; cursor: pointer; transition: background 0.15s; }
  .btn-primary { background: #111; color: #fff; }
  .btn-primary:hover { background: #333; }
  .error { background: #fef2f2; color: #b91c1c; padding: 0.75rem; border-radius: 8px; font-size: 0.8125rem; margin-bottom: 1rem; display: none; }
  .info { background: #f0f9ff; color: #1e40af; padding: 0.75rem; border-radius: 8px; font-size: 0.8125rem; margin-bottom: 1rem; }
  .divider { border-top: 1px solid #eee; margin: 1.5rem 0; }
</style>
</head>
<body>
<div class="card">
  <h1>{{app_name}}</h1>
  <p class="subtitle"><span class="client-name">{{client_name}}</span> wants to access your account</p>

  <div id="error" class="error">{{error_message}}</div>

  <div id="session-view" style="display:none;">
    <p class="info">You are signed in. Click below to authorize access.</p>
    <form method="POST" action="{{authorize_url}}">
      <input type="hidden" name="csrf_token" value="{{csrf_token}}">
      <input type="hidden" name="client_id" value="{{client_id}}">
      <input type="hidden" name="redirect_uri" value="{{redirect_uri}}">
      <input type="hidden" name="code_challenge" value="{{code_challenge}}">
      <input type="hidden" name="code_challenge_method" value="{{code_challenge_method}}">
      <input type="hidden" name="state" value="{{state}}">
      <input type="hidden" name="scope" value="{{scope}}">
      <input type="hidden" name="response_type" value="code">
      <button type="submit" class="btn btn-primary">Authorize</button>
    </form>
  </div>

  <div id="consent-view" style="display:none;">
    <p class="info">You are signed in. Click below to authorize access.</p>
    <form method="POST" action="{{authorize_url}}">
      <input type="hidden" name="csrf_token" value="{{csrf_token}}">
      <input type="hidden" name="client_id" value="{{client_id}}">
      <input type="hidden" name="redirect_uri" value="{{redirect_uri}}">
      <input type="hidden" name="code_challenge" value="{{code_challenge}}">
      <input type="hidden" name="code_challenge_method" value="{{code_challenge_method}}">
      <input type="hidden" name="state" value="{{state}}">
      <input type="hidden" name="scope" value="{{scope}}">
      <input type="hidden" name="response_type" value="code">
      <input type="hidden" id="token-field" name="token" value="">
      <button type="submit" class="btn btn-primary">Authorize</button>
    </form>
  </div>

  <div id="login-view" style="display:none;">
    <form method="POST" action="{{authorize_url}}">
      <input type="hidden" name="csrf_token" value="{{csrf_token}}">
      <input type="hidden" name="client_id" value="{{client_id}}">
      <input type="hidden" name="redirect_uri" value="{{redirect_uri}}">
      <input type="hidden" name="code_challenge" value="{{code_challenge}}">
      <input type="hidden" name="code_challenge_method" value="{{code_challenge_method}}">
      <input type="hidden" name="state" value="{{state}}">
      <input type="hidden" name="scope" value="{{scope}}">
      <input type="hidden" name="response_type" value="code">
      <div class="field">
        <label for="email">Email</label>
        <input type="email" id="email" name="email" required autocomplete="email">
      </div>
      <div class="field">
        <label for="password">Password</label>
        <input type="password" id="password" name="password" required autocomplete="current-password">
      </div>
      <button type="submit" class="btn btn-primary">Sign in & Authorize</button>
    </form>
  </div>

  <div id="external-view" style="display:none;">
    <p class="info">Please sign in to {{app_name}} in your browser first, then return here to authorize.</p>
  </div>
</div>

<script>
(function() {
  var authMode = '{{auth_mode}}';
  var errorEl = document.getElementById('error');
  if (errorEl.textContent.trim()) errorEl.style.display = 'block';

  if (authMode === 'session') {
    // User identified by HttpOnly session cookie (set by auth middleware)
    // No localStorage needed, works cross-origin
    document.getElementById('session-view').style.display = 'block';
  } else {
    // Fallback: try reading JWT from localStorage (only works same-origin)
    var token = null;
    var keys = ['forge_token', 'token', 'access_token', 'auth_token'];
    for (var i = 0; i < keys.length; i++) {
      var v = localStorage.getItem(keys[i]);
      if (v && v.length > 20) { token = v; break; }
    }

    if (token) {
      document.getElementById('token-field').value = token;
      document.getElementById('consent-view').style.display = 'block';
    } else if (authMode === 'hmac') {
      document.getElementById('login-view').style.display = 'block';
    } else {
      document.getElementById('external-view').style.display = 'block';
    }
  }
})();
</script>
</body>
</html>