Skip to content

Commit

Permalink
Replace /user/v1/whoami for /actor/v1/whoami
Browse files Browse the repository at this point in the history
Change-type: major
  • Loading branch information
otaviojacobi committed Aug 8, 2023
1 parent 79a4c9a commit c661577
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $ npm install --save balena-sdk

## Platforms

We currently support NodeJS (14+) and the browser.
We currently support NodeJS (16+) and the browser.

The following features are node-only:
- OS image streaming download (`balena.models.os.download`),
Expand Down
44 changes: 21 additions & 23 deletions src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import * as errors from 'balena-errors';
import memoizee from 'memoizee';
import type { InjectedDependenciesParam, InjectedOptionsParam } from '.';
import { WhoamiResult } from './types/auth';

const getAuth = function (
deps: InjectedDependenciesParam,
Expand Down Expand Up @@ -65,32 +66,26 @@ const getAuth = function (
opts,
);

interface WhoamiResult {
id: number;
username: string;
email: string;
}

const userWhoami = async () => {
const actorWhoami = async () => {
const { body } = await request.send<WhoamiResult>({
method: 'GET',
url: '/user/v1/whoami',
url: '/actor/v1/whoami',
baseUrl: apiUrl,
});
return body;
};

const memoizedUserWhoami = memoizee(userWhoami, {
const memoizedActorWhoami = memoizee(actorWhoami, {
primitive: true,
promise: true,
});

const getUserDetails = async (noCache = false) => {
const getActorDetails = async (noCache = false) => {
if (noCache) {
memoizedUserWhoami.clear();
memoizedActorWhoami.clear();
}
try {
return await memoizedUserWhoami();
return await memoizedActorWhoami();
} catch (err) {
throw normalizeAuthError(err);
}
Expand All @@ -117,10 +112,9 @@ const getAuth = function (
* }
* });
*/
async function whoami(): Promise<string | undefined> {
async function whoami(): Promise<WhoamiResult | undefined> {
try {
const userDetails = await getUserDetails();
return userDetails?.username;
return await getActorDetails();
} catch (err) {
if (err instanceof errors.BalenaNotLoggedIn) {
return;
Expand Down Expand Up @@ -203,7 +197,7 @@ const getAuth = function (
email: string;
password: string;
}): Promise<void> {
memoizedUserWhoami.clear();
memoizedActorWhoami.clear();
const token = await authenticate(credentials);
await auth.setKey(token);
}
Expand All @@ -224,7 +218,7 @@ const getAuth = function (
* balena.auth.loginWithToken(authToken);
*/
function loginWithToken(authToken: string): Promise<void> {
memoizedUserWhoami.clear();
memoizedActorWhoami.clear();
return auth.setKey(authToken);
}

Expand All @@ -249,7 +243,7 @@ const getAuth = function (
*/
async function isLoggedIn(): Promise<boolean> {
try {
await getUserDetails(true);
await getActorDetails(true);
return true;
} catch (err) {
if (
Expand Down Expand Up @@ -303,7 +297,7 @@ const getAuth = function (
* });
*/
async function getUserId(): Promise<number> {
const { id } = await getUserDetails();
const { id } = await getActorDetails();
return id;
}

Expand Down Expand Up @@ -352,9 +346,13 @@ const getAuth = function (
* console.log(email);
* });
*/
async function getEmail(): Promise<string> {
const { email } = await getUserDetails();
return email;
async function getEmail(): Promise<string | null> {
const result = await getActorDetails();

if (result.actorType === 'user') {
return result.email;
}
return null;
}

/**
Expand All @@ -370,7 +368,7 @@ const getAuth = function (
* balena.auth.logout();
*/
function logout(): Promise<void> {
memoizedUserWhoami.clear();
memoizedActorWhoami.clear();
return auth.removeKey();
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from './types/models';
export * from './types/jwt';
export * from './types/contract';
export * from './types/user-invite';
export * from './types/auth';

export type { Interceptor };
export type {
Expand Down
26 changes: 26 additions & 0 deletions src/types/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface UserKeyWhoAmIResponse {
id: number;
actorType: 'user';
actorTypeId: number;
username: string;
email: string | null;
}

export interface ApplicationKeyWhoAmIResponse {
id: number;
actorType: 'application';
actorTypeId: number;
slug: string;
}

export interface DeviceKeyWhoAmIResponse {
id: number;
actorType: 'device';
actorTypeId: number;
uuid: string;
}

export type WhoamiResult =
| UserKeyWhoAmIResponse
| ApplicationKeyWhoAmIResponse
| DeviceKeyWhoAmIResponse;
13 changes: 10 additions & 3 deletions tests/integration/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
givenLoggedInUserWithApiKey,
loginUserWith2FA,
} from './setup';
import { UserKeyWhoAmIResponse } from '../../src';

describe('SDK authentication', function () {
timeSuite(before);
Expand Down Expand Up @@ -239,8 +240,11 @@ describe('SDK authentication', function () {
});

describe('balena.auth.whoami()', () => {
it('should eventually be the username', async function () {
expect(await balena.auth.whoami()).to.equal(credentials.username);
it('should eventually be the user whoami response', async function () {
const whoamiResult =
(await balena.auth.whoami()) as UserKeyWhoAmIResponse;
expect(whoamiResult?.actorType).to.equal('user');
expect(whoamiResult?.username).to.equal(credentials.username);
});
});

Expand Down Expand Up @@ -285,7 +289,10 @@ describe('SDK authentication', function () {

describe('balena.auth.whoami()', () => {
it('should eventually be the username', async function () {
expect(await balena.auth.whoami()).to.equal(credentials.username);
const whoamiResult =
(await balena.auth.whoami()) as UserKeyWhoAmIResponse;
expect(whoamiResult?.actorType).to.equal('user');
expect(whoamiResult?.username).to.equal(credentials.username);
});
});

Expand Down
23 changes: 14 additions & 9 deletions tests/integration/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ import { Dictionary } from '../../typings/utils';
import { balena } from './setup';

export const getInitialOrganization = async () => {
const [org] = await balena.pine.get({
resource: 'organization',
options: {
$select: ['id', 'handle'],
$filter: {
handle: await balena.auth.whoami(),
const whoAmIResult = await balena.auth.whoami();

if (whoAmIResult?.actorType === 'user') {
const [org] = await balena.pine.get({
resource: 'organization',
options: {
$select: ['id', 'handle'],
$filter: {
handle: whoAmIResult.username,
},
},
},
});
});
return org;
}

return org;
throw new Error('Organization must be created with user api key');
};

export const getFieldLabel = (field: string | { [key: string]: string }) =>
Expand Down

0 comments on commit c661577

Please sign in to comment.