Feat: Implement JWT bearer token authentication
Protects image upload, delete, and tag-update endpoints behind Bearer token auth. Public read endpoints remain open. Angular SPA gains a login page, auth interceptor, and route guard for /upload. - JWTAuthProvider (HS256, sub/iat/exp, secrets.compare_digest) - POST /api/v1/auth/token login endpoint - require_auth FastAPI dependency on all write routes - AuthService, LoginComponent, authInterceptor, authGuard - Detail page hides write controls for unauthenticated visitors - 43 unit tests passing; integration tests require Docker stack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
55
ui/src/app/auth/auth.service.spec.ts
Normal file
55
ui/src/app/auth/auth.service.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
let httpMock: HttpTestingController;
|
||||
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [AuthService],
|
||||
});
|
||||
service = TestBed.inject(AuthService);
|
||||
httpMock = TestBed.inject(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.verify();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
it('login stores token in sessionStorage', (done) => {
|
||||
service.login('owner', 'password').subscribe(() => {
|
||||
expect(sessionStorage.getItem('auth_token')).toBe('test-token');
|
||||
done();
|
||||
});
|
||||
const req = httpMock.expectOne('/api/v1/auth/token');
|
||||
expect(req.request.method).toBe('POST');
|
||||
req.flush({ access_token: 'test-token', token_type: 'bearer', expires_in: 3600 });
|
||||
});
|
||||
|
||||
it('isAuthenticated returns true when token is present', () => {
|
||||
sessionStorage.setItem('auth_token', 'some-token');
|
||||
expect(service.isAuthenticated()).toBeTrue();
|
||||
});
|
||||
|
||||
it('isAuthenticated returns false when no token', () => {
|
||||
expect(service.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
|
||||
// US4 logout tests (T025)
|
||||
it('logout removes token from sessionStorage', () => {
|
||||
sessionStorage.setItem('auth_token', 'tok');
|
||||
service.logout();
|
||||
expect(sessionStorage.getItem('auth_token')).toBeNull();
|
||||
});
|
||||
|
||||
it('isAuthenticated returns false after logout', () => {
|
||||
sessionStorage.setItem('auth_token', 'tok');
|
||||
service.logout();
|
||||
expect(service.isAuthenticated()).toBeFalse();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user