Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### BREAKING CHANGES

- `current()` now expects the back-end to always respond with `200 OK` and a JSON body containing an `authenticated` discriminator. The body shape is:
- When logged in: `{ "authenticated": true, ...userFields }` — the entire body is stored as the `currentUser`.
- When not logged in: `{ "authenticated": false }` — the service is set to logged-out state.
- `current()` no longer rejects on `401`. The previous "401 means anonymous" workaround in consumers (`current().catch(r => r.status !== 401 ? throw : noop)`) is no longer required and should be removed.
- Requires `rest-secure-spring-boot-starter` 16.0.0 or newer (or any back-end that implements the discriminator contract on `GET /authentication/current`).

## [0.0.2] - 2019-09-25

### BREAKING CHANGES
Expand Down
5 changes: 4 additions & 1 deletion docs/introduction/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export class Login extends Component<Props, State> {
autoLoginFailed: false
};

// Calling `current()` automatically logs the user in when the session is still valid
// Calling `current()` automatically logs the user in when the session is still valid.
// `current()` always resolves on a successful (200) backend response: if the user is
// logged in, the service is populated; if not, the service is set to logged-out.
// It only rejects on transport or server errors (e.g. 5xx).
componentDidMount() {
current().catch(() => {
this.setState({ autoLoginFailed: true });
Expand Down
29 changes: 9 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 22 additions & 6 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,23 @@ export async function login(body: Record<string, unknown>): Promise<void> {
* The URL it will send the request to is defined by the 'currentUserUrl'
* from the Config object.
*
* An example of the response:
* The back-end is expected to always respond with 200 OK and a JSON body
* containing an `authenticated` discriminator. When the user is logged in,
* the entire body (including any user fields) is written to the
* AuthenticationService as the currentUser. When the user is not logged in,
* the service is set to logged-out state.
*
* An example of a logged-in response:
*
* ```JSON
* { "id": 1, "name": "sjonnyb", "roles": ["ADMIN"] }
* { "authenticated": true, "id": 1, "name": "sjonnyb", "roles": ["ADMIN"] }
* ```
*
* The entire response will be written to the Redux AuthenticationService's
* Whatever the JSON response is will be the currentUser.
* An example of an anonymous response:
*
* ```JSON
* { "authenticated": false }
* ```
*
* @returns { Promise } An empty promise.
*/
Expand All @@ -71,8 +80,15 @@ export async function current(): Promise<void> {
},
method: 'get'
});
const user = await tryParse(response);
service.login(user);
if (response.status !== 200) {
throw response;
}
const body = await response.json();
if (body?.authenticated) {
service.login(body);
} else {
service.logout();
}
}

/**
Expand Down
37 changes: 32 additions & 5 deletions tests/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ describe('AuthenticationService', () => {
});

describe('current', () => {
test('200', async () => {
expect.assertions(4);
test('200 authenticated', async () => {
expect.assertions(5);

const { loginSpy } = setup();
const { loginSpy, logoutSpy } = setup();

global.fetch = vi.fn().mockResolvedValue({
status: 200,
json: () => Promise.resolve({ fake: 'current' })
json: () => Promise.resolve({ authenticated: true, fake: 'current' })
});

await current();
Expand All @@ -95,7 +95,34 @@ describe('AuthenticationService', () => {
})
);
expect(loginSpy).toHaveBeenCalledTimes(1);
expect(loginSpy).toHaveBeenCalledWith({ fake: 'current' });
expect(loginSpy).toHaveBeenCalledWith({
authenticated: true,
fake: 'current'
});
expect(logoutSpy).toHaveBeenCalledTimes(0);
});

test('200 anonymous', async () => {
expect.assertions(4);

const { loginSpy, logoutSpy } = setup();

global.fetch = vi.fn().mockResolvedValue({
status: 200,
json: () => Promise.resolve({ authenticated: false })
});

await current();

expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith(
'/api/authentication/current',
expect.objectContaining({
method: 'get'
})
);
expect(loginSpy).toHaveBeenCalledTimes(0);
expect(logoutSpy).toHaveBeenCalledTimes(1);
});

test('500', async () => {
Expand Down
Loading