@@ -6,9 +6,20 @@ import { StringValue } from "ms";
66import { PrismaClient } from "../generated/client" ;
77import { config } from "../config" ;
88import { getTestPrisma , setupTestDb } from "./testUtils" ;
9+ import {
10+ ACCESS_TOKEN_COOKIE_NAME ,
11+ REFRESH_TOKEN_COOKIE_NAME ,
12+ } from "../auth/cookies" ;
913
1014describe ( "Link Sharing - Public By Drawing ID" , ( ) => {
1115 const userAgent = "vitest-link-sharing-public" ;
16+ const createRefreshToken = ( user : { id : string ; email : string } ) =>
17+ jwt . sign (
18+ { userId : user . id , email : user . email , type : "refresh" } ,
19+ config . jwtSecret ,
20+ { expiresIn : config . jwtRefreshExpiresIn as StringValue }
21+ ) ;
22+
1223 let prisma : PrismaClient ;
1324 let app : any ;
1425
@@ -34,6 +45,34 @@ describe("Link Sharing - Public By Drawing ID", () => {
3445 } ) ;
3546 } ;
3647
48+ const createLinkShare = async (
49+ drawingId : string ,
50+ permission : "view" | "edit"
51+ ) => {
52+ const response = await ownerAgent
53+ . post ( `/drawings/${ drawingId } /link-shares` )
54+ . set ( "User-Agent" , userAgent )
55+ . set ( "Authorization" , `Bearer ${ ownerToken } ` )
56+ . set ( ownerCsrfHeaderName , ownerCsrfToken )
57+ . send ( { permission } ) ;
58+
59+ expect ( response . status ) . toBe ( 200 ) ;
60+ return response ;
61+ } ;
62+
63+ const createAnonymousAgentWithCsrf = async ( ) => {
64+ const anonAgent = request . agent ( app ) ;
65+ const anonCsrfRes = await anonAgent
66+ . get ( "/csrf-token" )
67+ . set ( "User-Agent" , userAgent ) ;
68+
69+ return {
70+ anonAgent,
71+ anonCsrfHeaderName : anonCsrfRes . body . header as string ,
72+ anonCsrfToken : anonCsrfRes . body . token as string ,
73+ } ;
74+ } ;
75+
3776 beforeAll ( async ( ) => {
3877 setupTestDb ( ) ;
3978 prisma = getTestPrisma ( ) ;
@@ -88,14 +127,7 @@ describe("Link Sharing - Public By Drawing ID", () => {
88127
89128 it ( "allows anonymous GET when link-share policy is view" , async ( ) => {
90129 const drawing = await createDrawing ( ) ;
91-
92- const linkRes = await ownerAgent
93- . post ( `/drawings/${ drawing . id } /link-shares` )
94- . set ( "User-Agent" , userAgent )
95- . set ( "Authorization" , `Bearer ${ ownerToken } ` )
96- . set ( ownerCsrfHeaderName , ownerCsrfToken )
97- . send ( { permission : "view" } ) ;
98- expect ( linkRes . status ) . toBe ( 200 ) ;
130+ await createLinkShare ( drawing . id , "view" ) ;
99131
100132 const anonGet = await request ( app )
101133 . get ( `/drawings/${ drawing . id } ` )
@@ -104,12 +136,8 @@ describe("Link Sharing - Public By Drawing ID", () => {
104136 expect ( anonGet . body ?. id ) . toBe ( drawing . id ) ;
105137 expect ( anonGet . body ?. accessLevel ) . toBe ( "view" ) ;
106138
107- const anonAgent = request . agent ( app ) ;
108- const anonCsrfRes = await anonAgent
109- . get ( "/csrf-token" )
110- . set ( "User-Agent" , userAgent ) ;
111- const anonCsrfHeaderName = anonCsrfRes . body . header ;
112- const anonCsrfToken = anonCsrfRes . body . token ;
139+ const { anonAgent, anonCsrfHeaderName, anonCsrfToken } =
140+ await createAnonymousAgentWithCsrf ( ) ;
113141
114142 const anonPut = await anonAgent
115143 . put ( `/drawings/${ drawing . id } ` )
@@ -119,23 +147,45 @@ describe("Link Sharing - Public By Drawing ID", () => {
119147 expect ( anonPut . status ) . toBe ( 404 ) ;
120148 } ) ;
121149
122- it ( "allows anonymous PUT when link-share policy is edit " , async ( ) => {
150+ it ( "still allows anonymous GET when a stale access-token cookie is present " , async ( ) => {
123151 const drawing = await createDrawing ( ) ;
152+ await createLinkShare ( drawing . id , "view" ) ;
124153
125- const linkRes = await ownerAgent
126- . post ( `/drawings/${ drawing . id } /link-shares ` )
154+ const response = await request ( app )
155+ . get ( `/drawings/${ drawing . id } ` )
127156 . set ( "User-Agent" , userAgent )
128- . set ( "Authorization" , `Bearer ${ ownerToken } ` )
129- . set ( ownerCsrfHeaderName , ownerCsrfToken )
130- . send ( { permission : "edit" } ) ;
131- expect ( linkRes . status ) . toBe ( 200 ) ;
157+ . set (
158+ "Cookie" ,
159+ ` ${ ACCESS_TOKEN_COOKIE_NAME } = ${ createRefreshToken ( ownerUser ) } `
160+ ) ;
132161
133- const anonAgent = request . agent ( app ) ;
134- const anonCsrfRes = await anonAgent
135- . get ( "/csrf-token" )
136- . set ( "User-Agent" , userAgent ) ;
137- const anonCsrfHeaderName = anonCsrfRes . body . header ;
138- const anonCsrfToken = anonCsrfRes . body . token ;
162+ expect ( response . status ) . toBe ( 200 ) ;
163+ expect ( response . body ?. id ) . toBe ( drawing . id ) ;
164+ expect ( response . body ?. accessLevel ) . toBe ( "view" ) ;
165+ } ) ;
166+
167+ it ( "still allows anonymous GET when only a refresh-token cookie is present" , async ( ) => {
168+ const drawing = await createDrawing ( ) ;
169+ await createLinkShare ( drawing . id , "view" ) ;
170+
171+ const response = await request ( app )
172+ . get ( `/drawings/${ drawing . id } ` )
173+ . set ( "User-Agent" , userAgent )
174+ . set (
175+ "Cookie" ,
176+ `${ REFRESH_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
177+ ) ;
178+
179+ expect ( response . status ) . toBe ( 200 ) ;
180+ expect ( response . body ?. id ) . toBe ( drawing . id ) ;
181+ expect ( response . body ?. accessLevel ) . toBe ( "view" ) ;
182+ } ) ;
183+
184+ it ( "allows anonymous PUT when link-share policy is edit" , async ( ) => {
185+ const drawing = await createDrawing ( ) ;
186+ await createLinkShare ( drawing . id , "edit" ) ;
187+ const { anonAgent, anonCsrfHeaderName, anonCsrfToken } =
188+ await createAnonymousAgentWithCsrf ( ) ;
139189
140190 const anonPut = await anonAgent
141191 . put ( `/drawings/${ drawing . id } ` )
@@ -147,6 +197,97 @@ describe("Link Sharing - Public By Drawing ID", () => {
147197 expect ( anonPut . body ?. name ) . toBe ( "Renamed By Anonymous" ) ;
148198 } ) ;
149199
200+ it ( "still allows anonymous PUT when edit link-share is active and a stale access-token cookie is present" , async ( ) => {
201+ const drawing = await createDrawing ( ) ;
202+ await createLinkShare ( drawing . id , "edit" ) ;
203+ const { anonAgent, anonCsrfHeaderName, anonCsrfToken } =
204+ await createAnonymousAgentWithCsrf ( ) ;
205+
206+ const response = await anonAgent
207+ . put ( `/drawings/${ drawing . id } ` )
208+ . set ( "User-Agent" , userAgent )
209+ . set (
210+ "Cookie" ,
211+ `${ ACCESS_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
212+ )
213+ . set ( anonCsrfHeaderName , anonCsrfToken )
214+ . send ( { name : "Edited With Stale Cookie" } ) ;
215+
216+ expect ( response . status ) . toBe ( 200 ) ;
217+ expect ( response . body ?. id ) . toBe ( drawing . id ) ;
218+ expect ( response . body ?. name ) . toBe ( "Edited With Stale Cookie" ) ;
219+ } ) ;
220+
221+ it ( "still allows anonymous PUT when only a refresh-token cookie is present" , async ( ) => {
222+ const drawing = await createDrawing ( ) ;
223+ await createLinkShare ( drawing . id , "edit" ) ;
224+ const { anonAgent, anonCsrfHeaderName, anonCsrfToken } =
225+ await createAnonymousAgentWithCsrf ( ) ;
226+
227+ const response = await anonAgent
228+ . put ( `/drawings/${ drawing . id } ` )
229+ . set ( "User-Agent" , userAgent )
230+ . set (
231+ "Cookie" ,
232+ `${ REFRESH_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
233+ )
234+ . set ( anonCsrfHeaderName , anonCsrfToken )
235+ . send ( { name : "Edited With Refresh Cookie" } ) ;
236+
237+ expect ( response . status ) . toBe ( 200 ) ;
238+ expect ( response . body ?. id ) . toBe ( drawing . id ) ;
239+ expect ( response . body ?. name ) . toBe ( "Edited With Refresh Cookie" ) ;
240+ } ) ;
241+
242+ it ( "returns 401 for a private drawing when a stale access-token cookie is present" , async ( ) => {
243+ const drawing = await createDrawing ( ) ;
244+
245+ const response = await request ( app )
246+ . get ( `/drawings/${ drawing . id } ` )
247+ . set ( "User-Agent" , userAgent )
248+ . set (
249+ "Cookie" ,
250+ `${ ACCESS_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
251+ ) ;
252+
253+ expect ( response . status ) . toBe ( 401 ) ;
254+ expect ( response . body ?. message ) . toBe ( "Invalid or expired token" ) ;
255+ } ) ;
256+
257+ it ( "returns 401 for a private drawing when only a refresh-token cookie is present" , async ( ) => {
258+ const drawing = await createDrawing ( ) ;
259+
260+ const response = await request ( app )
261+ . get ( `/drawings/${ drawing . id } ` )
262+ . set ( "User-Agent" , userAgent )
263+ . set (
264+ "Cookie" ,
265+ `${ REFRESH_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
266+ ) ;
267+
268+ expect ( response . status ) . toBe ( 401 ) ;
269+ expect ( response . body ?. message ) . toBe ( "Invalid or expired token" ) ;
270+ } ) ;
271+
272+ it ( "returns 401 for a private drawing PUT when a stale access-token cookie is present" , async ( ) => {
273+ const drawing = await createDrawing ( ) ;
274+ const { anonAgent, anonCsrfHeaderName, anonCsrfToken } =
275+ await createAnonymousAgentWithCsrf ( ) ;
276+
277+ const response = await anonAgent
278+ . put ( `/drawings/${ drawing . id } ` )
279+ . set ( "User-Agent" , userAgent )
280+ . set (
281+ "Cookie" ,
282+ `${ ACCESS_TOKEN_COOKIE_NAME } =${ createRefreshToken ( ownerUser ) } `
283+ )
284+ . set ( anonCsrfHeaderName , anonCsrfToken )
285+ . send ( { name : "Should Fail" } ) ;
286+
287+ expect ( response . status ) . toBe ( 401 ) ;
288+ expect ( response . body ?. message ) . toBe ( "Invalid or expired token" ) ;
289+ } ) ;
290+
150291 it ( "revokes previous active link-share when creating a new one" , async ( ) => {
151292 const drawing = await createDrawing ( ) ;
152293
@@ -180,4 +321,3 @@ describe("Link Sharing - Public By Drawing ID", () => {
180321 expect ( activeCount ) . toBe ( 1 ) ;
181322 } ) ;
182323} ) ;
183-
0 commit comments