@@ -303,6 +303,75 @@ func TestAuth_Tokens_RequiresAuth(t *testing.T) {
303303 require .Equal (t , http .StatusUnauthorized , rr .Code )
304304}
305305
306+ // TestAuth_ApiToken_AuthenticatesRequests proves an API token created via
307+ // the tokens endpoint actually authenticates requests (the bug in #162 was
308+ // that tokens were issued but never accepted by RequireAuth).
309+ func TestAuth_ApiToken_AuthenticatesRequests (t * testing.T ) {
310+ ts := testutil .NewTestServer (t )
311+ user := testutil .CreateUser (t , ts .DB )
312+ session := testutil .LoginAs (t , ts .DB , user )
313+
314+ rr := ts .POST ("/api/users/me/tokens/" , map [string ]any {"label" : "ci-token" }, session )
315+ require .Equal (t , http .StatusCreated , rr .Code , "body=%s" , rr .Body .String ())
316+ plainToken , _ := testutil .MustJSONMap (t , rr )["token" ].(string )
317+ require .NotEmpty (t , plainToken )
318+
319+ rr2 := ts .DoWithHeaders (http .MethodGet , "/api/users/me/" , nil , http.Header {
320+ "Authorization" : []string {"Bearer " + plainToken },
321+ })
322+ require .Equal (t , http .StatusOK , rr2 .Code , "body=%s" , rr2 .Body .String ())
323+ assert .Equal (t , user .ID .String (), testutil .MustJSONMap (t , rr2 )["id" ])
324+ }
325+
326+ // TestAuth_ApiToken_RevokedRejected proves a revoked token no longer
327+ // authenticates.
328+ func TestAuth_ApiToken_RevokedRejected (t * testing.T ) {
329+ ts := testutil .NewTestServer (t )
330+ user := testutil .CreateUser (t , ts .DB )
331+ session := testutil .LoginAs (t , ts .DB , user )
332+
333+ rr := ts .POST ("/api/users/me/tokens/" , map [string ]any {"label" : "ci-token" }, session )
334+ require .Equal (t , http .StatusCreated , rr .Code , "body=%s" , rr .Body .String ())
335+ createdBody := testutil .MustJSONMap (t , rr )
336+ plainToken , _ := createdBody ["token" ].(string )
337+ require .NotEmpty (t , plainToken )
338+
339+ listRR := ts .GET ("/api/users/me/tokens/" , session )
340+ tokens , _ := testutil .MustJSONMap (t , listRR )["tokens" ].([]any )
341+ require .Len (t , tokens , 1 )
342+ tokenID , _ := tokens [0 ].(map [string ]any )["id" ].(string )
343+ require .NotEmpty (t , tokenID )
344+
345+ revokeRR := ts .DELETE ("/api/users/me/tokens/" + tokenID + "/" , session )
346+ require .Equal (t , http .StatusNoContent , revokeRR .Code )
347+
348+ rr2 := ts .DoWithHeaders (http .MethodGet , "/api/users/me/" , nil , http.Header {
349+ "Authorization" : []string {"Bearer " + plainToken },
350+ })
351+ require .Equal (t , http .StatusUnauthorized , rr2 .Code , "body=%s" , rr2 .Body .String ())
352+ }
353+
354+ // TestAuth_ApiToken_DeactivatedUserRejected proves a deactivated user's
355+ // still-valid API token is rejected, mirroring the #155 protection for
356+ // cookie sessions.
357+ func TestAuth_ApiToken_DeactivatedUserRejected (t * testing.T ) {
358+ ts := testutil .NewTestServer (t )
359+ user := testutil .CreateUser (t , ts .DB )
360+ session := testutil .LoginAs (t , ts .DB , user )
361+
362+ rr := ts .POST ("/api/users/me/tokens/" , map [string ]any {"label" : "ci-token" }, session )
363+ require .Equal (t , http .StatusCreated , rr .Code , "body=%s" , rr .Body .String ())
364+ plainToken , _ := testutil .MustJSONMap (t , rr )["token" ].(string )
365+ require .NotEmpty (t , plainToken )
366+
367+ require .NoError (t , ts .DB .Exec ("UPDATE users SET is_active = false WHERE id = ?" , user .ID ).Error )
368+
369+ rr2 := ts .DoWithHeaders (http .MethodGet , "/api/users/me/" , nil , http.Header {
370+ "Authorization" : []string {"Bearer " + plainToken },
371+ })
372+ require .Equal (t , http .StatusUnauthorized , rr2 .Code , "body=%s" , rr2 .Body .String ())
373+ }
374+
306375func TestAuth_ForgotPassword_NoSMTPReturns503 (t * testing.T ) {
307376 ts := testutil .NewTestServer (t )
308377 testutil .CreateUser (t , ts .DB , testutil .WithUserEmail ("forgot@test.local" ))
0 commit comments