@@ -2,6 +2,11 @@ import { describe, it, expect } from "vitest";
22import jwt from "jsonwebtoken" ;
33import NeetoJWT from "../src" ;
44import { generateES256KeyPair } from "./utils" ;
5+ import {
6+ CONSUMER_LOGIN_PATH ,
7+ SCOPES ,
8+ USER_LOGIN_PATH ,
9+ } from "../src/constants.js" ;
510
611const { privateKey, publicKey } = generateES256KeyPair ( ) ;
712
@@ -42,21 +47,22 @@ describe("NeetoJWT", () => {
4247 const decoded = jwt . verify ( token , publicKey , { algorithms : [ "ES256" ] } ) ;
4348 expect ( decoded . email ) . toBe ( email ) ;
4449 expect ( decoded . workspace ) . toBe ( workspace ) ;
45- expect ( decoded . scope ) . toBe ( " user" ) ;
50+ expect ( decoded . scope ) . toBe ( SCOPES . user ) ;
4651 expect ( decoded . iat ) . toBeDefined ( ) ;
4752 expect ( decoded . exp ) . toBeDefined ( ) ;
4853 } ) ;
4954
5055 it ( "should embed scope in the JWT payload for consumer scope" , ( ) => {
5156 const neetoJWT = new NeetoJWT ( {
5257 email,
58+ workspace,
5359 privateKey,
54- scope : " consumer" ,
60+ scope : SCOPES . consumer ,
5561 } ) ;
5662 const token = neetoJWT . generateJWT ( ) ;
5763 const decoded = jwt . verify ( token , publicKey , { algorithms : [ "ES256" ] } ) ;
58- expect ( decoded . scope ) . toBe ( " consumer" ) ;
59- expect ( decoded . workspace ) . toBe ( "app" ) ;
64+ expect ( decoded . scope ) . toBe ( SCOPES . consumer ) ;
65+ expect ( decoded . workspace ) . toBe ( workspace ) ;
6066 } ) ;
6167
6268 it ( "should generate a login URL" , ( ) => {
@@ -80,36 +86,34 @@ describe("NeetoJWT", () => {
8086 } ) ;
8187
8288 it ( "should use environment variables for workspace and privateKey if not provided" , ( ) => {
83- process . env . NEETO_JWT_WORKSPACE = "spinkart" ;
89+ process . env . NEETO_JWT_WORKSPACE = workspace ;
8490 process . env . NEETO_JWT_PRIVATE_KEY = privateKey ;
8591
8692 const neetoJWT = new NeetoJWT ( { email } ) ;
8793 const token = neetoJWT . generateJWT ( ) ;
8894 expect ( token ) . toBeDefined ( ) ;
8995
90- const decoded = jwt . verify ( token , publicKey , {
91- algorithms : [ "ES256" ] ,
92- } ) ;
96+ const decoded = jwt . verify ( token , publicKey , { algorithms : [ "ES256" ] } ) ;
9397 expect ( decoded . workspace ) . toBe ( process . env . NEETO_JWT_WORKSPACE ) ;
9498 } ) ;
9599
96100 it ( "should default to user scope and produce a /users/auth/jwt URL" , ( ) => {
97101 const neetoJWT = new NeetoJWT ( { email, workspace, privateKey } ) ;
98102 const loginUrl = neetoJWT . generateLoginUrl ( redirectUri ) ;
99- expect ( loginUrl ) . toContain ( "/users/auth/jwt" ) ;
100- expect ( loginUrl ) . not . toContain ( "/consumers/auth/jwt" ) ;
103+ expect ( loginUrl ) . toContain ( USER_LOGIN_PATH ) ;
104+ expect ( loginUrl ) . not . toContain ( CONSUMER_LOGIN_PATH ) ;
101105 } ) ;
102106
103107 it ( "should produce a /consumers/auth/jwt URL when scope is 'consumer'" , ( ) => {
104108 const neetoJWT = new NeetoJWT ( {
105109 email,
106110 workspace : "app" ,
107111 privateKey,
108- scope : " consumer" ,
112+ scope : SCOPES . consumer ,
109113 } ) ;
110114 const loginUrl = neetoJWT . generateLoginUrl ( redirectUri ) ;
111- expect ( loginUrl ) . toContain ( "/consumers/auth/jwt" ) ;
112- expect ( loginUrl ) . not . toContain ( "/users/auth/jwt" ) ;
115+ expect ( loginUrl ) . toContain ( CONSUMER_LOGIN_PATH ) ;
116+ expect ( loginUrl ) . not . toContain ( USER_LOGIN_PATH ) ;
113117 expect ( loginUrl ) . toContain ( "https://app.neetoauth.com/consumers/auth/jwt" ) ;
114118 } ) ;
115119
@@ -118,10 +122,10 @@ describe("NeetoJWT", () => {
118122 email,
119123 workspace,
120124 privateKey,
121- scope : " user" ,
125+ scope : SCOPES . user ,
122126 } ) ;
123127 const loginUrl = neetoJWT . generateLoginUrl ( redirectUri ) ;
124- expect ( loginUrl ) . toContain ( "/users/auth/jwt" ) ;
128+ expect ( loginUrl ) . toContain ( USER_LOGIN_PATH ) ;
125129 } ) ;
126130
127131 it ( "should throw if scope is anything other than 'user' or 'consumer'" , ( ) => {
@@ -134,21 +138,45 @@ describe("NeetoJWT", () => {
134138 // @ts -expect-error: invalid scope passed deliberately to assert runtime guard.
135139 scope : "admin" ,
136140 } )
137- ) . toThrow ( " Scope must be either 'user' or 'consumer'." ) ;
141+ ) . toThrow ( ` Scope must be one of: ${ Object . values ( SCOPES ) . join ( ", " ) } ` ) ;
138142 } ) ;
139143
140- it ( "should default consumer-scope workspace to ' app' when omitted, ignoring NEETO_JWT_WORKSPACE" , ( ) => {
144+ it ( "should always use the global app auth host for consumer scope, even when workspace comes from NEETO_JWT_WORKSPACE" , ( ) => {
141145 const previous = process . env . NEETO_JWT_WORKSPACE ;
142146 process . env . NEETO_JWT_WORKSPACE = "tenant1" ;
143147 try {
144- const neetoJWT = new NeetoJWT ( { email, privateKey, scope : "consumer" } ) ;
148+ const neetoJWT = new NeetoJWT ( {
149+ email,
150+ privateKey,
151+ scope : SCOPES . consumer ,
152+ } ) ;
145153 const loginUrl = neetoJWT . generateLoginUrl (
146154 "http://partner.example.com/post-login"
147155 ) ;
148- expect ( loginUrl ) . toContain ( "https://app.neetoauth.com/consumers/auth/jwt" ) ;
149- expect ( loginUrl ) . not . toContain ( "tenant1" ) ;
156+ expect ( loginUrl ) . toContain (
157+ "https://app.neetoauth.com/consumers/auth/jwt"
158+ ) ;
159+
160+ const token = new URL ( loginUrl ) . searchParams . get ( "jwt" ) as string ;
161+ const payload = jwt . decode ( token ) ;
162+ expect ( payload . workspace ) . toBe ( "tenant1" ) ;
163+ } finally {
164+ if ( previous === undefined ) delete process . env . NEETO_JWT_WORKSPACE ;
165+ else process . env . NEETO_JWT_WORKSPACE = previous ;
166+ }
167+ } ) ;
168+
169+ it ( "should throw when consumer scope is used without workspace or NEETO_JWT_WORKSPACE" , ( ) => {
170+ const previous = process . env . NEETO_JWT_WORKSPACE ;
171+ delete process . env . NEETO_JWT_WORKSPACE ;
172+
173+ try {
174+ expect (
175+ ( ) => new NeetoJWT ( { email, privateKey, scope : SCOPES . consumer } )
176+ ) . toThrow ( "Workspace is required." ) ;
150177 } finally {
151- process . env . NEETO_JWT_WORKSPACE = previous ;
178+ if ( previous === undefined ) delete process . env . NEETO_JWT_WORKSPACE ;
179+ else process . env . NEETO_JWT_WORKSPACE = previous ;
152180 }
153181 } ) ;
154182
@@ -157,17 +185,12 @@ describe("NeetoJWT", () => {
157185 email,
158186 privateKey,
159187 workspace : "spinkart" ,
160- scope : " consumer" ,
188+ scope : SCOPES . consumer ,
161189 } ) ;
162190 const loginUrl = neetoJWT . generateLoginUrl ( "http://partner.example.com/cb" ) ;
163- expect ( loginUrl ) . toContain (
164- "https://app.neetoauth.com/consumers/auth/jwt"
165- ) ;
166-
191+ expect ( loginUrl ) . toContain ( "https://app.neetoauth.com/consumers/auth/jwt" ) ;
167192 const token = new URL ( loginUrl ) . searchParams . get ( "jwt" ) as string ;
168- const payload = JSON . parse (
169- Buffer . from ( token . split ( "." ) [ 1 ] , "base64" ) . toString ( )
170- ) ;
193+ const payload = jwt . decode ( token ) ;
171194 expect ( payload . workspace ) . toBe ( "spinkart" ) ;
172195 } ) ;
173196
@@ -176,7 +199,7 @@ describe("NeetoJWT", () => {
176199 email,
177200 workspace : "app" ,
178201 privateKey,
179- scope : " consumer" ,
202+ scope : SCOPES . consumer ,
180203 } ) ;
181204 const loginUrl = neetoJWT . generateLoginUrl (
182205 "http://partner.example.com/path with space?q=1"
0 commit comments