diff --git a/README.md b/README.md index a53cf06df..73ef1ebbf 100644 --- a/README.md +++ b/README.md @@ -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`), diff --git a/src/auth.ts b/src/auth.ts index 1440f7ffd..c883a89b2 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -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, @@ -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({ 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); } @@ -117,10 +112,9 @@ const getAuth = function ( * } * }); */ - async function whoami(): Promise { + async function whoami(): Promise { try { - const userDetails = await getUserDetails(); - return userDetails?.username; + return await getActorDetails(); } catch (err) { if (err instanceof errors.BalenaNotLoggedIn) { return; @@ -203,7 +197,7 @@ const getAuth = function ( email: string; password: string; }): Promise { - memoizedUserWhoami.clear(); + memoizedActorWhoami.clear(); const token = await authenticate(credentials); await auth.setKey(token); } @@ -224,7 +218,7 @@ const getAuth = function ( * balena.auth.loginWithToken(authToken); */ function loginWithToken(authToken: string): Promise { - memoizedUserWhoami.clear(); + memoizedActorWhoami.clear(); return auth.setKey(authToken); } @@ -249,7 +243,7 @@ const getAuth = function ( */ async function isLoggedIn(): Promise { try { - await getUserDetails(true); + await getActorDetails(true); return true; } catch (err) { if ( @@ -303,7 +297,7 @@ const getAuth = function ( * }); */ async function getUserId(): Promise { - const { id } = await getUserDetails(); + const { id } = await getActorDetails(); return id; } @@ -352,9 +346,13 @@ const getAuth = function ( * console.log(email); * }); */ - async function getEmail(): Promise { - const { email } = await getUserDetails(); - return email; + async function getEmail(): Promise { + const result = await getActorDetails(); + + if (result.actorType === 'user') { + return result.email; + } + return null; } /** @@ -370,7 +368,7 @@ const getAuth = function ( * balena.auth.logout(); */ function logout(): Promise { - memoizedUserWhoami.clear(); + memoizedActorWhoami.clear(); return auth.removeKey(); } diff --git a/src/index.ts b/src/index.ts index a7ade0143..0e5e2f883 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 { diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 000000000..b5f28acb9 --- /dev/null +++ b/src/types/auth.ts @@ -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; diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index fb2653361..179183798 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -10,6 +10,7 @@ import { givenLoggedInUserWithApiKey, loginUserWith2FA, } from './setup'; +import { UserKeyWhoAmIResponse } from '../../src'; describe('SDK authentication', function () { timeSuite(before); @@ -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); }); }); @@ -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); }); }); diff --git a/tests/integration/utils.ts b/tests/integration/utils.ts index d21030e7d..8478c1994 100644 --- a/tests/integration/utils.ts +++ b/tests/integration/utils.ts @@ -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 }) =>