@tensorgrad/oauth 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Small OAuth client SDK for integrating tensorGrad SSO into first-party apps.
7
7
  - creates `state` values
8
8
  - creates PKCE verifier/challenge pairs
9
9
  - builds tensorGrad `/oauth/authorize` URLs
10
+ - supports `prompt=login` for account switching
10
11
  - exchanges authorization codes at `/oauth/token`
11
12
  - fetches canonical identity from `/oauth/userinfo`
12
13
  - normalizes tensorGrad user payloads
@@ -45,6 +46,7 @@ console.log(TENSORGRAD_ISSUER);
45
46
 
46
47
  ```ts
47
48
  import {
49
+ DEFAULT_TG_SCOPES,
48
50
  TENSORGRAD_ISSUER,
49
51
  assertGrantedScopes,
50
52
  createAuthorizationUrl,
@@ -84,7 +86,7 @@ export default {
84
86
  },
85
87
  {
86
88
  redirectUri,
87
- scope: ["openid", "profile", "email"],
89
+ scope: DEFAULT_TG_SCOPES,
88
90
  state,
89
91
  codeChallenge: pkce.codeChallenge,
90
92
  codeChallengeMethod: pkce.codeChallengeMethod
@@ -99,9 +101,21 @@ export default {
99
101
  }
100
102
 
101
103
  if (url.pathname === "/auth/callback") {
104
+ const oauthError = url.searchParams.get("error");
105
+ const oauthErrorDescription = url.searchParams.get("error_description");
102
106
  const code = url.searchParams.get("code");
103
107
  const state = url.searchParams.get("state");
104
108
 
109
+ if (oauthError) {
110
+ return Response.json(
111
+ {
112
+ error: oauthError,
113
+ errorDescription: oauthErrorDescription ?? null
114
+ },
115
+ { status: 400 }
116
+ );
117
+ }
118
+
105
119
  if (!code || !state) {
106
120
  return new Response("Missing code or state", { status: 400 });
107
121
  }
@@ -112,6 +126,7 @@ export default {
112
126
  const expectedState = "<persisted-state>";
113
127
  const codeVerifier = "<persisted-code-verifier>";
114
128
 
129
+ // Use a constant-time comparison in your app when comparing secrets.
115
130
  if (state !== expectedState) {
116
131
  return new Response("Invalid state", { status: 400 });
117
132
  }
@@ -125,7 +140,7 @@ export default {
125
140
  codeVerifier
126
141
  });
127
142
 
128
- assertGrantedScopes(token.scope, ["openid", "profile", "email"]);
143
+ assertGrantedScopes(token.scope, DEFAULT_TG_SCOPES);
129
144
 
130
145
  const userFromToken = normalizeTgUser(token.user);
131
146
  const userFromUserInfo = normalizeTgUser(
@@ -149,6 +164,10 @@ export default {
149
164
 
150
165
  - `openid` is mandatory and the library enforces that when building authorize URLs.
151
166
  - tensorGrad currently uses `https://www.tensorgrad.com` as the OAuth provider.
167
+ - `DEFAULT_TG_SCOPES` is the recommended scope set for first-party apps.
152
168
  - `assertGrantedScopes(token.scope, ["admin"])` is the simplest way to require admin access.
169
+ - pass `prompt: "login"` to `createAuthorizationUrl(...)` when you need tensorGrad to force a fresh account selection.
170
+ - handle `error` / `error_description` on the callback before validating `code`
171
+ - compare persisted `state` values using a constant-time comparison in your app
153
172
  - `isAdminScopeGranted(token.scope)` is available when you only need a boolean check.
154
173
  - app session and logout handling stay in the consuming app.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare const TENSORGRAD_ISSUER = "https://www.tensorgrad.com";
2
+ export declare const DEFAULT_TG_SCOPES: readonly ["openid", "profile", "email"];
2
3
  export interface tgOAuthConfig {
3
4
  issuer: string;
4
5
  clientId: string;
@@ -13,6 +14,7 @@ export interface AuthorizationUrlOptions {
13
14
  redirectUri: string;
14
15
  scope: string[] | string;
15
16
  state: string;
17
+ prompt?: "login";
16
18
  codeChallenge?: string;
17
19
  codeChallengeMethod?: "S256" | "plain";
18
20
  extraParams?: Record<string, string>;
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export const TENSORGRAD_ISSUER = "https://www.tensorgrad.com";
2
+ export const DEFAULT_TG_SCOPES = ["openid", "profile", "email"];
2
3
  const MIN_STATE_BYTES = 16;
3
4
  const MAX_RANDOM_BYTES = 96;
4
5
  const MIN_PKCE_RANDOM_BYTES = 32;
@@ -89,6 +90,9 @@ export function createAuthorizationUrl(config, options) {
89
90
  url.searchParams.set("response_type", "code");
90
91
  url.searchParams.set("scope", scope);
91
92
  url.searchParams.set("state", state);
93
+ if (options.prompt) {
94
+ url.searchParams.set("prompt", options.prompt);
95
+ }
92
96
  if (options.codeChallenge) {
93
97
  url.searchParams.set("code_challenge", requireNonEmptyString("codeChallenge", options.codeChallenge));
94
98
  url.searchParams.set("code_challenge_method", options.codeChallengeMethod ?? "S256");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensorgrad/oauth",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Small OAuth client SDK for tensorGrad SSO integrations.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,6 +27,10 @@
27
27
  "tensorgrad",
28
28
  "sso"
29
29
  ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/tensorGrad/tgAuth"
33
+ },
30
34
  "license": "MIT",
31
35
  "devDependencies": {
32
36
  "typescript": "^5.9.2"