From fbf59557e8be2d5822473fba07fbc77b60558c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Tue, 8 Aug 2023 09:52:53 -0300 Subject: [PATCH 1/9] **BREAKING**: Drop support to node < 16 Node 14 may well still work with the SDK for quite a while, but we'll no longer actively test against them since they are now characterized as EOL, and it's quite possible that it may stop working entirely in any future release. Change-type: major --- .github/actions/test/action.yml | 4 ++-- README.md | 2 +- package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index 3516c42cc..dfa679a40 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -12,11 +12,11 @@ inputs: runs: using: "composite" steps: - - name: Setup Node.js 14 + - name: Setup Node.js 16 if: ${{ env.os_value == 'ubuntu-20.04' }} uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 - name: Setup Node.js lts if: ${{ env.os_value != 'ubuntu-20.04' }} diff --git a/README.md b/README.md index e27d9ff8e..7de206652 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/package.json b/package.json index 70c5821de..e32c0ae31 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "author": "Juan Cruz Viotti ", "license": "Apache-2.0", "engines": { - "node": ">=14.0" + "node": ">=16.0" }, "devDependencies": { "@balena/lint": "^6.1.1", @@ -119,7 +119,7 @@ "dependencies": { "@balena/es-version": "^1.0.0", "@types/json-schema": "^7.0.9", - "@types/node": "^14.0.0", + "@types/node": "^16.0.0", "abortcontroller-polyfill": "^1.7.1", "balena-auth": "^5.1.0", "balena-errors": "^4.8.0", From 86954863b65506aca8465025c310cca770448a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Tue, 8 Aug 2023 12:37:21 -0300 Subject: [PATCH 2/9] **BREAKING**: Support non-user API keys in auth.isLoggedIn() & whoami() Change-type: major --- DOCUMENTATION.md | 12 +- src/auth.ts | 66 +++---- src/index.ts | 1 + src/types/auth.ts | 26 +++ tests/integration/auth.spec.ts | 168 +++++++++++++++++- .../models/organization-membership.spec.ts | 6 +- tests/integration/setup.ts | 40 ++++- tests/integration/utils.ts | 24 ++- 8 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 src/types/auth.ts diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 448b5786c..d133b99fa 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -6630,19 +6630,19 @@ balena.auth.loginWithToken(token); #### auth.whoami() ⇒ Promise -This will only work if you used [login](#balena.auth.login) to log in. +This will only work if you used [login](#balena.auth.login) or [loginWithToken](#balena.auth.loginWithToken) to log in. **Kind**: static method of [auth](#balena.auth) -**Summary**: Return current logged in username +**Summary**: Return current logged in information **Access**: public -**Fulfil**: (String\|undefined) - username, if it exists +**Fulfil**: (Object\|undefined) - actor information, if it exists **Example** ```js -balena.auth.whoami().then(function(username) { - if (!username) { +balena.auth.whoami().then(function(result) { + if (!result) { console.log('I\'m not logged in!'); } else { - console.log('My username is:', username); + console.log('My result is:', result); } }); ``` diff --git a/src/auth.ts b/src/auth.ts index 1440f7ffd..865a7f9d8 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,62 +66,55 @@ 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); } }; /** - * @summary Return current logged in username + * @summary Return current logged in information * @name whoami * @public * @function * @memberof balena.auth * - * @description This will only work if you used {@link balena.auth.login} to log in. + * @description This will only work if you used {@link balena.auth.login} or {@link balena.auth.loginWithToken} to log in. * - * @fulfil {(String|undefined)} - username, if it exists + * @fulfil {(Object|undefined)} - actor information, if it exists * @returns {Promise} * * @example - * balena.auth.whoami().then(function(username) { - * if (!username) { + * balena.auth.whoami().then(function(result) { + * if (!result) { * console.log('I\'m not logged in!'); * } else { - * console.log('My username is:', username); + * console.log('My result is:', result); * } * }); */ - 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,8 +297,14 @@ const getAuth = function ( * }); */ async function getUserId(): Promise { - const { id } = await getUserDetails(); - return id; + const actor = await getActorDetails(); + + if (actor.actorType !== 'user') { + throw new Error( + 'The authentication credentials in use are not of a user', + ); + } + return actor.actorTypeId; } /** @@ -352,9 +352,15 @@ const getAuth = function ( * console.log(email); * }); */ - async function getEmail(): Promise { - const { email } = await getUserDetails(); - return email; + async function getEmail(): Promise { + const actor = await getActorDetails(); + + if (actor.actorType !== 'user') { + throw new Error( + 'The authentication credentials in use are not of a user', + ); + } + return actor.email; } /** @@ -370,7 +376,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 4e1984a17..6c85da092 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..f22abb222 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -9,7 +9,14 @@ import { givenLoggedInUser, givenLoggedInUserWithApiKey, loginUserWith2FA, + givenLoggedInWithADeviceApiKey, + givenLoggedInWithAnApplicationApiKey, } from './setup'; +import { + UserKeyWhoAmIResponse, + DeviceKeyWhoAmIResponse, + ApplicationKeyWhoAmIResponse, +} from '../../src'; describe('SDK authentication', function () { timeSuite(before); @@ -239,8 +246,16 @@ 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); + expect(whoamiResult?.email).to.equal(credentials.email); + expect(whoamiResult).to.have.property('id').that.is.a('number'); + expect(whoamiResult) + .to.have.property('actorTypeId') + .that.is.a('number'); }); }); @@ -274,7 +289,142 @@ describe('SDK authentication', function () { }); }); - describe('when logged in with API key', function () { + describe('when logged in with a device API Key', function () { + givenLoggedInWithADeviceApiKey(before); + + describe('balena.auth.isLoggedIn()', () => { + it('should eventually be true', async function () { + expect(await balena.auth.isLoggedIn()).to.be.true; + }); + }); + + describe('balena.auth.whoami()', () => { + it('should eventually be the device whoami response', async function () { + const whoamiResult = + (await balena.auth.whoami()) as DeviceKeyWhoAmIResponse; + expect(whoamiResult?.actorType).to.equal('device'); + expect(whoamiResult).to.have.property('uuid').that.is.a('string'); + expect(whoamiResult).to.have.property('id').that.is.a('number'); + expect(whoamiResult) + .to.have.property('actorTypeId') + .that.is.a('number'); + }); + }); + + describe('balena.auth.getEmail()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getEmail(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.getUserId()', () => { + it('should be rejected with an error', async () => { + const promise = balena.auth.getUserId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.getUserActorId()', () => { + it('should be rejected with an error', async () => { + const promise = balena.auth.getUserActorId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.logout()', function () { + it('should logout the user', async () => { + await balena.auth.logout(); + expect(await balena.auth.isLoggedIn()).to.be.false; + }); + + it('...should reset the token on logout', async () => { + const promise = balena.auth.getToken(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + }); + + describe('when logged in with an application API Key', function () { + givenLoggedInWithAnApplicationApiKey(before); + + describe('balena.auth.isLoggedIn()', () => { + it('should eventually be true', async function () { + expect(await balena.auth.isLoggedIn()).to.be.true; + }); + }); + + describe('balena.auth.whoami()', () => { + it('should eventually be the application whoami response', async function () { + const whoamiResult = + (await balena.auth.whoami()) as ApplicationKeyWhoAmIResponse; + expect(whoamiResult?.actorType).to.equal('application'); + expect(whoamiResult).to.have.property('slug').that.is.a('string'); + expect(whoamiResult).to.have.property('id').that.is.a('number'); + expect(whoamiResult) + .to.have.property('actorTypeId') + .that.is.a('number'); + }); + }); + + describe('balena.auth.getEmail()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getEmail(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.getUserId()', () => { + it('should be rejected with an error', async () => { + const promise = balena.auth.getUserId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.getUserActorId()', () => { + it('should be rejected with an error', async () => { + const promise = balena.auth.getUserActorId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + + describe('balena.auth.logout()', function () { + it('should logout the user', async () => { + await balena.auth.logout(); + expect(await balena.auth.isLoggedIn()).to.be.false; + }); + + it('...should reset the token on logout', async () => { + const promise = balena.auth.getToken(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + }); + describe('when logged in with an user API key', function () { givenLoggedInUserWithApiKey(before); describe('balena.auth.isLoggedIn()', () => { @@ -284,8 +434,16 @@ 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); + expect(whoamiResult?.email).to.equal(credentials.email); + expect(whoamiResult).to.have.property('id').that.is.a('number'); + expect(whoamiResult) + .to.have.property('actorTypeId') + .that.is.a('number'); }); }); diff --git a/tests/integration/models/organization-membership.spec.ts b/tests/integration/models/organization-membership.spec.ts index 8c7a3deda..fd2988be6 100644 --- a/tests/integration/models/organization-membership.spec.ts +++ b/tests/integration/models/organization-membership.spec.ts @@ -16,6 +16,7 @@ import { itShouldGetAllTagsByResource, } from './tags'; import type * as tagsHelper from './tags'; +import { UserKeyWhoAmIResponse } from '../../../src'; const keyAlternatives = [ ['id', (member: Pick) => member.id], @@ -43,7 +44,10 @@ describe('Organization Membership Model', function () { let ctx: Mocha.Context; before(async function () { ctx = this; - this.username = await balena.auth.whoami(); + const userInfoResult = + (await balena.auth.whoami()) as UserKeyWhoAmIResponse; + expect(userInfoResult?.actorType).to.be.eq('user'); + this.username = userInfoResult.username; this.userId = await balena.auth.getUserId(); const roles = await balena.pine.get({ resource: 'organization_membership_role', diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index 2e7e7cdbc..3c6ded9bb 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -179,7 +179,7 @@ export function givenLoggedInUserWithApiKey(beforeFn: Mocha.HookFunction) { afterFn(() => resetUser()); } -export function givenLoggedInUser(beforeFn: Mocha.HookFunction) { +export function givenLoggedInUser(beforeFn: Mocha.HookFunction, forceRelogin = false) { beforeFn(async () => { await balena.auth.login({ email: credentials.email, @@ -189,7 +189,16 @@ export function givenLoggedInUser(beforeFn: Mocha.HookFunction) { }); const afterFn = beforeFn === beforeEach ? afterEach : after; - afterFn(() => resetUser()); + afterFn(async () => { + if (forceRelogin) { + await balena.auth.logout(); + await balena.auth.login({ + email: credentials.email, + password: credentials.password, + }); + } + return resetUser(); + }); } export function loginUserWith2FA() { @@ -349,6 +358,33 @@ export const testDeviceOsInfo = { supervisor_version: '10.8.0', }; +export function givenLoggedInWithAnApplicationApiKey( + beforeFn: Mocha.HookFunction, +) { + givenLoggedInUser(beforeFn, true); + givenAnApplication(beforeFn); + + beforeFn(async function () { + const key = await balena.models.application.generateProvisioningKey( + this.application.slug, + ); + await balena.auth.logout(); + await balena.auth.loginWithToken(key); + }); +} + +export function givenLoggedInWithADeviceApiKey(beforeFn: Mocha.HookFunction) { + givenLoggedInUser(beforeFn, true); + givenAnApplication(beforeFn); + givenADevice(beforeFn); + + beforeFn(async function () { + const key = await balena.models.device.generateDeviceKey(this.device.id); + await balena.auth.logout(); + await balena.auth.loginWithToken(key); + }); +} + export function givenADevice( beforeFn: Mocha.HookFunction, extraDeviceProps?: BalenaSdk.PineSubmitBody, diff --git a/tests/integration/utils.ts b/tests/integration/utils.ts index d21030e7d..40fcadbd1 100644 --- a/tests/integration/utils.ts +++ b/tests/integration/utils.ts @@ -2,17 +2,23 @@ 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 can only be filtered with user api key'); }; export const getFieldLabel = (field: string | { [key: string]: string }) => From 88ab4ebedbada148d2d992086110c07332254db5 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Thu, 10 Aug 2023 15:03:30 +0300 Subject: [PATCH 3/9] **BREAKING**: Drop pre-Resin OS v1 device.os_version normalization Change-type: major --- src/models/application.ts | 18 ++++-------------- src/models/device.ts | 12 ++---------- src/util/device-os-version.ts | 16 ---------------- 3 files changed, 6 insertions(+), 40 deletions(-) diff --git a/src/models/application.ts b/src/models/application.ts index 99b7fd725..d3e759fa8 100644 --- a/src/models/application.ts +++ b/src/models/application.ts @@ -46,7 +46,6 @@ import { withSupervisorLockedError, } from '../util'; -import { normalizeDeviceOsVersion } from '../util/device-os-version'; import { getCurrentServiceDetailsPineExpand, generateCurrentServiceDetails, @@ -185,15 +184,6 @@ const getApplicationModel = function ( } }; - const normalizeApplication = function (application: Application) { - if (Array.isArray(application.owns__device)) { - application.owns__device.forEach((device) => - normalizeDeviceOsVersion(device), - ); - } - return application; - }; - const isDirectlyAccessibleByUserFilter = { is_directly_accessible_by__user: { $any: { @@ -265,7 +255,7 @@ const getApplicationModel = function ( options ?? {}, ), }); - return apps.map(normalizeApplication); + return apps; }, /** @@ -365,7 +355,7 @@ const getApplicationModel = function ( if (application == null) { throw new errors.BalenaApplicationNotFound(slugOrUuidOrId); } - return normalizeApplication(application); + return application; }, /** @@ -517,7 +507,7 @@ const getApplicationModel = function ( throw new errors.BalenaAmbiguousApplication(appName); } const [application] = applications; - return normalizeApplication(application); + return application; }, /** @@ -560,7 +550,7 @@ const getApplicationModel = function ( if (application == null) { throw new errors.BalenaApplicationNotFound(`${owner}/${appName}`); } - return normalizeApplication(application); + return application; }, /** diff --git a/src/models/device.ts b/src/models/device.ts index 8f48a5022..ae82ea5a0 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -57,7 +57,6 @@ import { import { getDeviceOsSemverWithVariant, ensureVersionCompatibility, - normalizeDeviceOsVersion, } from '../util/device-os-version'; import { getCurrentServiceDetailsPineExpand, @@ -230,13 +229,6 @@ const getDeviceModel = function ( } }; - const addExtraInfo = function < - T extends Parameters[0], - >(device: T) { - normalizeDeviceOsVersion(device); - return device; - }; - const getAppliedConfigVariableValue = async ( uuidOrId: string | number, name: string, @@ -333,7 +325,7 @@ const getDeviceModel = function ( resource: 'device', options: mergePineOptions({ $orderby: 'device_name asc' }, options), }); - return devices.map(addExtraInfo) as Device[]; + return devices as Device[]; } async function startOsUpdate( @@ -638,7 +630,7 @@ const getDeviceModel = function ( if (device == null) { throw new errors.BalenaDeviceNotFound(uuidOrId); } - return addExtraInfo(device) as Device; + return device as Device; }, /** diff --git a/src/util/device-os-version.ts b/src/util/device-os-version.ts index 74440b8ee..08cfce3f2 100644 --- a/src/util/device-os-version.ts +++ b/src/util/device-os-version.ts @@ -1,21 +1,5 @@ import bSemver = require('balena-semver'); import type * as BalenaSdk from '..'; -import { isProvisioned } from './device'; - -// TODO: Drop in the next major -export const normalizeDeviceOsVersion = ( - device: Partial< - Pick & Parameters[0] - >, -) => { - if ( - device.os_version != null && - device.os_version.length === 0 && - isProvisioned(device) - ) { - device.os_version = 'Resin OS 1.0.0-pre'; - } -}; export const getDeviceOsSemverWithVariant = ({ os_version, From d5c588e987c6d5eca6b9f540a287466db883810e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Thu, 10 Aug 2023 17:14:45 -0300 Subject: [PATCH 4/9] auth: Add getUserInfo Change-type: minor --- DOCUMENTATION.md | 17 ++++++++++ src/auth.ts | 35 +++++++++++++++++++- src/types/auth.ts | 6 ++++ tests/integration/auth.spec.ts | 60 ++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index d133b99fa..1a91d218a 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -429,6 +429,7 @@ const sdk = fromSharedOptions(); * [.loginWithToken(authToken)](#balena.auth.loginWithToken) ⇒ Promise * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise + * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.getEmail()](#balena.auth.getEmail) ⇒ Promise @@ -6483,6 +6484,7 @@ balena.models.billing.downloadInvoice(orgId, '0000').then(function(stream) { * [.loginWithToken(authToken)](#balena.auth.loginWithToken) ⇒ Promise * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise + * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.getEmail()](#balena.auth.getEmail) ⇒ Promise @@ -6740,6 +6742,21 @@ balena.auth.getToken().then(function(token) { console.log(token); }); ``` + + +#### auth.getUserInfo() ⇒ Promise +This will only work if you used [login](#balena.auth.login) to log in. + +**Kind**: static method of [auth](#balena.auth) +**Summary**: Get current logged in user's info +**Access**: public +**Fulfil**: Object - user info +**Example** +```js +balena.auth.getUserInfo().then(function(userInfo) { + console.log(userInfo); +}); +``` #### auth.getUserId() ⇒ Promise diff --git a/src/auth.ts b/src/auth.ts index 865a7f9d8..6846426fc 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -17,7 +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'; +import { UserInfo, WhoamiResult } from './types/auth'; const getAuth = function ( deps: InjectedDependenciesParam, @@ -279,6 +279,38 @@ const getAuth = function ( }); } + /** + * @summary Get current logged in user's info + * @name getUserInfo + * @public + * @function + * @memberof balena.auth + * + * @description This will only work if you used {@link balena.auth.login} to log in. + * + * @fulfil {Object} - user info + * @returns {Promise} + * + * @example + * balena.auth.getUserInfo().then(function(userInfo) { + * console.log(userInfo); + * }); + */ + async function getUserInfo(): Promise { + const actor = await getActorDetails(); + + if (actor.actorType !== 'user') { + throw new Error( + 'The authentication credentials in use are not of a user', + ); + } + return { + id: actor.actorTypeId, + email: actor.email, + username: actor.username, + }; + } + /** * @summary Get current logged in user's id * @name getUserId @@ -501,6 +533,7 @@ const getAuth = function ( getUserId, getUserActorId, getEmail, + getUserInfo, logout, register, verifyEmail, diff --git a/src/types/auth.ts b/src/types/auth.ts index b5f28acb9..0f0dc257b 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -24,3 +24,9 @@ export type WhoamiResult = | UserKeyWhoAmIResponse | ApplicationKeyWhoAmIResponse | DeviceKeyWhoAmIResponse; + +export interface UserInfo { + id: number; + username: string; + email: string | null; +} diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index f22abb222..74e441575 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -99,6 +99,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getUserInfo(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + describe('balena.auth.getUserId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserId(); @@ -215,6 +225,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getUserInfo(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + describe('balena.auth.getUserId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserId(); @@ -265,6 +285,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const userInfo = await balena.auth.getUserInfo(); + expect(userInfo.email).to.equal(credentials.email); + expect(userInfo.username).to.equal(credentials.username); + expect(userInfo.id).to.be.a('number'); + expect(userInfo.id).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserId(); @@ -321,6 +351,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getUserInfo(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + describe('balena.auth.getUserId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserId(); @@ -389,6 +429,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getUserInfo(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'message', + 'The authentication credentials in use are not of a user', + ); + }); + }); + describe('balena.auth.getUserId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserId(); @@ -453,6 +503,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getUserInfo()', () => { + it('should be rejected with an error', async function () { + const userInfo = await balena.auth.getUserInfo(); + expect(userInfo.email).to.equal(credentials.email); + expect(userInfo.username).to.equal(credentials.username); + expect(userInfo.id).to.be.a('number'); + expect(userInfo.id).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserId(); From 84d9eb93022036bb3e0e2491e833592acf4c4f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Thu, 10 Aug 2023 18:17:49 -0300 Subject: [PATCH 5/9] **BREAKING**: Drop auth.getEmail in favor of auth.getUserInfo Change-type: major --- DOCUMENTATION.md | 17 ----------- src/auth.ts | 29 ------------------- tests/integration/auth.spec.ts | 52 ---------------------------------- 3 files changed, 98 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 1a91d218a..18bb19aa1 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -432,7 +432,6 @@ const sdk = fromSharedOptions(); * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise - * [.getEmail()](#balena.auth.getEmail) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise * [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise @@ -6487,7 +6486,6 @@ balena.models.billing.downloadInvoice(orgId, '0000').then(function(stream) { * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise - * [.getEmail()](#balena.auth.getEmail) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise * [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise @@ -6787,21 +6785,6 @@ balena.auth.getUserActorId().then(function(userActorId) { console.log(userActorId); }); ``` - - -#### auth.getEmail() ⇒ Promise -This will only work if you used [login](#balena.auth.login) to log in. - -**Kind**: static method of [auth](#balena.auth) -**Summary**: Get current logged in user's email -**Access**: public -**Fulfil**: String - user email -**Example** -```js -balena.auth.getEmail().then(function(email) { - console.log(email); -}); -``` #### auth.logout() ⇒ Promise diff --git a/src/auth.ts b/src/auth.ts index 6846426fc..518ffcc61 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -367,34 +367,6 @@ const getAuth = function ( return actor; } - /** - * @summary Get current logged in user's email - * @name getEmail - * @public - * @function - * @memberof balena.auth - * - * @description This will only work if you used {@link balena.auth.login} to log in. - * - * @fulfil {String} - user email - * @returns {Promise} - * - * @example - * balena.auth.getEmail().then(function(email) { - * console.log(email); - * }); - */ - async function getEmail(): Promise { - const actor = await getActorDetails(); - - if (actor.actorType !== 'user') { - throw new Error( - 'The authentication credentials in use are not of a user', - ); - } - return actor.email; - } - /** * @summary Logout * @name logout @@ -532,7 +504,6 @@ const getAuth = function ( getToken, getUserId, getUserActorId, - getEmail, getUserInfo, logout, register, diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index 74e441575..69e9749d0 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -89,16 +89,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getEmail(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserInfo(); @@ -215,16 +205,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getEmail(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserInfo(); @@ -279,12 +259,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should eventually be the email', async function () { - expect(await balena.auth.getEmail()).to.equal(credentials.email); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const userInfo = await balena.auth.getUserInfo(); @@ -341,16 +315,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getEmail(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserInfo(); @@ -419,16 +383,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getEmail(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserInfo(); @@ -497,12 +451,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getEmail()', () => { - it('should eventually be the email', async function () { - expect(await balena.auth.getEmail()).to.equal(credentials.email); - }); - }); - describe('balena.auth.getUserInfo()', () => { it('should be rejected with an error', async function () { const userInfo = await balena.auth.getUserInfo(); From dfc59eced18c9b08c95e6e76196ca153b6085cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Thu, 10 Aug 2023 18:36:48 -0300 Subject: [PATCH 6/9] **BREAKING**: Drop auth.getUserId in favor of auth.getUserInfo Change-type: major --- DOCUMENTATION.md | 17 ------ src/auth.ts | 34 +---------- src/models/device.ts | 43 +++++++------- src/models/key.ts | 2 +- tests/integration/auth.spec.ts | 58 +------------------ tests/integration/models/application.spec.ts | 8 ++- .../models/organization-membership.spec.ts | 8 +-- tests/integration/models/release.spec.ts | 6 +- tests/integration/setup.ts | 4 +- 9 files changed, 40 insertions(+), 140 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 18bb19aa1..e29b131d4 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -430,7 +430,6 @@ const sdk = fromSharedOptions(); * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise - * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise @@ -6484,7 +6483,6 @@ balena.models.billing.downloadInvoice(orgId, '0000').then(function(stream) { * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise - * [.getUserId()](#balena.auth.getUserId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise @@ -6755,21 +6753,6 @@ balena.auth.getUserInfo().then(function(userInfo) { console.log(userInfo); }); ``` - - -#### auth.getUserId() ⇒ Promise -This will only work if you used [login](#balena.auth.login) to log in. - -**Kind**: static method of [auth](#balena.auth) -**Summary**: Get current logged in user's id -**Access**: public -**Fulfil**: Number - user id -**Example** -```js -balena.auth.getUserId().then(function(userId) { - console.log(userId); -}); -``` #### auth.getUserActorId() ⇒ Promise diff --git a/src/auth.ts b/src/auth.ts index 518ffcc61..8591d002e 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -311,34 +311,6 @@ const getAuth = function ( }; } - /** - * @summary Get current logged in user's id - * @name getUserId - * @public - * @function - * @memberof balena.auth - * - * @description This will only work if you used {@link balena.auth.login} to log in. - * - * @fulfil {Number} - user id - * @returns {Promise} - * - * @example - * balena.auth.getUserId().then(function(userId) { - * console.log(userId); - * }); - */ - async function getUserId(): Promise { - const actor = await getActorDetails(); - - if (actor.actorType !== 'user') { - throw new Error( - 'The authentication credentials in use are not of a user', - ); - } - return actor.actorTypeId; - } - /** * @summary Get current logged in user's actor id * @name getUserActorId @@ -357,9 +329,10 @@ const getAuth = function ( * }); */ async function getUserActorId(): Promise { + const { id } = await getUserInfo(); const { actor } = (await pine.get({ resource: 'user', - id: await getUserId(), + id, options: { $select: 'actor', }, @@ -484,7 +457,7 @@ const getAuth = function ( * */ async function requestVerificationEmail() { - const id = await getUserId(); + const { id } = await getUserInfo(); await pine.patch({ resource: 'user', id, @@ -502,7 +475,6 @@ const getAuth = function ( loginWithToken, isLoggedIn, getToken, - getUserId, getUserActorId, getUserInfo, logout, diff --git a/src/models/device.ts b/src/models/device.ts index ae82ea5a0..e75dfb017 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -1362,28 +1362,29 @@ const getDeviceModel = function ( $expand: { is_for__device_type: deviceTypeOptions }, } as const; - const [userId, apiKey, application, deviceType] = await Promise.all([ - sdkInstance.auth.getUserId(), - sdkInstance.models.application.generateProvisioningKey( - applicationSlugOrUuidOrId, - ), - sdkInstance.models.application.get( - applicationSlugOrUuidOrId, - applicationOptions, - ) as Promise>, - typeof deviceTypeSlug === 'string' - ? (sdkInstance.models.deviceType.get(deviceTypeSlug, { - $select: 'slug', - $expand: { - is_of__cpu_architecture: { - $select: 'slug', + const [{ id: userId }, apiKey, application, deviceType] = + await Promise.all([ + sdkInstance.auth.getUserInfo(), + sdkInstance.models.application.generateProvisioningKey( + applicationSlugOrUuidOrId, + ), + sdkInstance.models.application.get( + applicationSlugOrUuidOrId, + applicationOptions, + ) as Promise>, + typeof deviceTypeSlug === 'string' + ? (sdkInstance.models.deviceType.get(deviceTypeSlug, { + $select: 'slug', + $expand: { + is_of__cpu_architecture: { + $select: 'slug', + }, }, - }, - }) as Promise< - PineTypedResult - >) - : null, - ]); + }) as Promise< + PineTypedResult + >) + : null, + ]); if (deviceType != null) { const isCompatibleParameter = sdkInstance.models.os.isArchitectureCompatibleWith( diff --git a/src/models/key.ts b/src/models/key.ts index a7c2de2dd..883de3a63 100644 --- a/src/models/key.ts +++ b/src/models/key.ts @@ -123,7 +123,7 @@ const getKeyModel = function (deps: InjectedDependenciesParam) { // Avoid ugly whitespaces key = key.trim(); - const userId = await sdkInstance.auth.getUserId(); + const { id: userId } = await sdkInstance.auth.getUserInfo(); return await pine.post({ resource: 'user__has__public_key', body: { diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index 69e9749d0..e56a149f2 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -99,16 +99,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getUserId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserActorId(); @@ -125,7 +115,7 @@ describe('SDK authentication', function () { email: credentials.register.email, password: credentials.register.password, }); - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); await balena.request.send({ method: 'DELETE', url: `/v2/user(${userId})`, @@ -215,16 +205,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getUserId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserActorId(); @@ -269,14 +249,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should eventually be a user id', async () => { - const userId = await balena.auth.getUserId(); - expect(userId).to.be.a('number'); - expect(userId).to.be.greaterThan(0); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserActorId(); @@ -325,16 +297,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should be rejected with an error', async () => { - const promise = balena.auth.getUserId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserActorId(); @@ -393,16 +355,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should be rejected with an error', async () => { - const promise = balena.auth.getUserId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserActorId(); @@ -461,14 +413,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserId()', () => { - it('should eventually be a user id', async () => { - const userId = await balena.auth.getUserId(); - expect(userId).to.be.a('number'); - expect(userId).to.be.greaterThan(0); - }); - }); - describe('balena.auth.getUserActorId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserActorId(); diff --git a/tests/integration/models/application.spec.ts b/tests/integration/models/application.spec.ts index 519b70d49..ee3bb07ea 100644 --- a/tests/integration/models/application.spec.ts +++ b/tests/integration/models/application.spec.ts @@ -1122,7 +1122,7 @@ describe('Application Model', function () { describe('given two releases', function () { before(async function () { - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); this.oldRelease = await balena.pine.post({ resource: 'release', body: { @@ -1350,11 +1350,12 @@ describe('Application Model', function () { [ 'draft', async function () { + const { id: userId } = await balena.auth.getUserInfo(); this.testNonLatestRelease = await balena.pine.post({ resource: 'release', body: { belongs_to__application: this.application.id, - is_created_by__user: await balena.auth.getUserId(), + is_created_by__user: userId, commit: 'draft-release-commit', status: 'success', source: 'cloud', @@ -1368,11 +1369,12 @@ describe('Application Model', function () { [ 'invalidated', async function () { + const { id } = await balena.auth.getUserInfo(); this.testNonLatestRelease = await balena.pine.post({ resource: 'release', body: { belongs_to__application: this.application.id, - is_created_by__user: await balena.auth.getUserId(), + is_created_by__user: id, commit: 'invalidated-release-commit', status: 'success', source: 'cloud', diff --git a/tests/integration/models/organization-membership.spec.ts b/tests/integration/models/organization-membership.spec.ts index fd2988be6..c72217079 100644 --- a/tests/integration/models/organization-membership.spec.ts +++ b/tests/integration/models/organization-membership.spec.ts @@ -16,7 +16,6 @@ import { itShouldGetAllTagsByResource, } from './tags'; import type * as tagsHelper from './tags'; -import { UserKeyWhoAmIResponse } from '../../../src'; const keyAlternatives = [ ['id', (member: Pick) => member.id], @@ -44,11 +43,10 @@ describe('Organization Membership Model', function () { let ctx: Mocha.Context; before(async function () { ctx = this; - const userInfoResult = - (await balena.auth.whoami()) as UserKeyWhoAmIResponse; - expect(userInfoResult?.actorType).to.be.eq('user'); + const userInfoResult = await balena.auth.getUserInfo(); this.username = userInfoResult.username; - this.userId = await balena.auth.getUserId(); + this.userId = userInfoResult.id; + const roles = await balena.pine.get({ resource: 'organization_membership_role', options: { $select: ['id', 'name'] }, diff --git a/tests/integration/models/release.spec.ts b/tests/integration/models/release.spec.ts index bd50cf144..a9ead85a9 100644 --- a/tests/integration/models/release.spec.ts +++ b/tests/integration/models/release.spec.ts @@ -265,7 +265,7 @@ describe('Release Model', function () { const testReleaseByField: Dictionary = {}; before(async function () { - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); await Promise.all( uniquePropertyNames.map(async (field, i) => { const fieldKey = getFieldLabel(field); @@ -633,7 +633,7 @@ describe('Release Model', function () { describe('balena.models.release.getLatestByApplication()', function () { before(async function () { ctx = this; - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); for (const body of [ { @@ -693,7 +693,7 @@ describe('Release Model', function () { describe('given two releases that share the same commit root', function () { before(async function () { const { application } = this; - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); await balena.pine.post({ resource: 'release', body: { diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index 3c6ded9bb..fda0fd404 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -216,7 +216,7 @@ export function loginPaidUser() { } async function resetInitialOrganization() { - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); const initialOrg = await getInitialOrganization(); await balena.pine.delete({ resource: 'organization_membership', @@ -495,7 +495,7 @@ export function givenMulticontainerApplication(beforeFn: Mocha.HookFunction) { givenAnApplication(beforeFn); beforeFn(async function () { - const userId = await balena.auth.getUserId(); + const { id: userId } = await balena.auth.getUserInfo(); const oldDate = new Date('2017-01-01').toISOString(); const now = new Date().toISOString(); const [webService, dbService, [oldRelease, newRelease]] = await Promise.all( From 4ee63a51b4f63d05eb5ef00c5da1c3960113d315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Thu, 10 Aug 2023 18:57:18 -0300 Subject: [PATCH 7/9] auth: Add getActorId Change-type: minor --- DOCUMENTATION.md | 17 +++++++++++ src/auth.ts | 22 ++++++++++++++ tests/integration/auth.spec.ts | 52 ++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index e29b131d4..e851810cb 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -430,6 +430,7 @@ const sdk = fromSharedOptions(); * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise + * [.getActorId()](#balena.auth.getActorId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise @@ -6483,6 +6484,7 @@ balena.models.billing.downloadInvoice(orgId, '0000').then(function(stream) { * [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise + * [.getActorId()](#balena.auth.getActorId) ⇒ Promise * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise @@ -6753,6 +6755,21 @@ balena.auth.getUserInfo().then(function(userInfo) { console.log(userInfo); }); ``` + + +#### auth.getActorId() ⇒ Promise +This will only work if you used [login](#balena.auth.login) or [loginWithToken](#balena.auth.loginWithToken) to log in. + +**Kind**: static method of [auth](#balena.auth) +**Summary**: Get current logged in actor id +**Access**: public +**Fulfil**: Number - actor id +**Example** +```js +balena.auth.getActorId().then(function(actorId) { + console.log(actorId); +}); +``` #### auth.getUserActorId() ⇒ Promise diff --git a/src/auth.ts b/src/auth.ts index 8591d002e..940769e7a 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -311,6 +311,27 @@ const getAuth = function ( }; } + /** + * @summary Get current logged in actor id + * @name getActorId + * @public + * @function + * @memberof balena.auth + * + * @description This will only work if you used {@link balena.auth.login} or {@link balena.auth.loginWithToken} to log in. + * + * @fulfil {Number} - actor id + * @returns {Promise} + * + * @example + * balena.auth.getActorId().then(function(actorId) { + * console.log(actorId); + * }); + */ + async function getActorId(): Promise { + return (await getActorDetails()).id; + } + /** * @summary Get current logged in user's actor id * @name getUserActorId @@ -475,6 +496,7 @@ const getAuth = function ( loginWithToken, isLoggedIn, getToken, + getActorId, getUserActorId, getUserInfo, logout, diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index e56a149f2..eed848402 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -99,6 +99,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getActorId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserActorId(); @@ -205,6 +215,16 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should be rejected with an error', async function () { + const promise = balena.auth.getActorId(); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaNotLoggedIn', + ); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async function () { const promise = balena.auth.getUserActorId(); @@ -249,6 +269,14 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should eventually be an actor id', async () => { + const userId = await balena.auth.getActorId(); + expect(userId).to.be.a('number'); + expect(userId).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserActorId(); @@ -297,6 +325,14 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should eventually be an actor id', async () => { + const userId = await balena.auth.getActorId(); + expect(userId).to.be.a('number'); + expect(userId).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserActorId(); @@ -355,6 +391,14 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should eventually be an actor id', async () => { + const userId = await balena.auth.getActorId(); + expect(userId).to.be.a('number'); + expect(userId).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should be rejected with an error', async () => { const promise = balena.auth.getUserActorId(); @@ -413,6 +457,14 @@ describe('SDK authentication', function () { }); }); + describe('balena.auth.getActorId()', () => { + it('should eventually be an actor id', async () => { + const userId = await balena.auth.getActorId(); + expect(userId).to.be.a('number'); + expect(userId).to.be.greaterThan(0); + }); + }); + describe('balena.auth.getUserActorId()', () => { it('should eventually be a user id', async () => { const userId = await balena.auth.getUserActorId(); From b82bfb5aaba1db60c8aa2108a26e5887ec25ba12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Jacobi?= Date: Thu, 10 Aug 2023 19:06:28 -0300 Subject: [PATCH 8/9] **BREAKING**: Drop auth.getUserActorId in favor of auth.getActorId Change-type: major --- DOCUMENTATION.md | 17 ------- src/auth.ts | 30 ------------- src/models/api-key.ts | 2 +- tests/integration/auth.spec.ts | 56 ------------------------ tests/integration/models/api-key.spec.ts | 4 +- tests/integration/setup.ts | 2 +- 6 files changed, 4 insertions(+), 107 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index e851810cb..407f615da 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -431,7 +431,6 @@ const sdk = fromSharedOptions(); * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getActorId()](#balena.auth.getActorId) ⇒ Promise - * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise * [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise @@ -6485,7 +6484,6 @@ balena.models.billing.downloadInvoice(orgId, '0000').then(function(stream) { * [.getToken()](#balena.auth.getToken) ⇒ Promise * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise * [.getActorId()](#balena.auth.getActorId) ⇒ Promise - * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise * [.logout()](#balena.auth.logout) ⇒ Promise * [.register(credentials)](#balena.auth.register) ⇒ Promise * [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise @@ -6770,21 +6768,6 @@ balena.auth.getActorId().then(function(actorId) { console.log(actorId); }); ``` - - -#### auth.getUserActorId() ⇒ Promise -This will only work if you used [login](#balena.auth.login) to log in. - -**Kind**: static method of [auth](#balena.auth) -**Summary**: Get current logged in user's actor id -**Access**: public -**Fulfil**: Number - user id -**Example** -```js -balena.auth.getUserActorId().then(function(userActorId) { - console.log(userActorId); -}); -``` #### auth.logout() ⇒ Promise diff --git a/src/auth.ts b/src/auth.ts index 940769e7a..a47d440e6 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -332,35 +332,6 @@ const getAuth = function ( return (await getActorDetails()).id; } - /** - * @summary Get current logged in user's actor id - * @name getUserActorId - * @public - * @function - * @memberof balena.auth - * - * @description This will only work if you used {@link balena.auth.login} to log in. - * - * @fulfil {Number} - user id - * @returns {Promise} - * - * @example - * balena.auth.getUserActorId().then(function(userActorId) { - * console.log(userActorId); - * }); - */ - async function getUserActorId(): Promise { - const { id } = await getUserInfo(); - const { actor } = (await pine.get({ - resource: 'user', - id, - options: { - $select: 'actor', - }, - }))!; - return actor; - } - /** * @summary Logout * @name logout @@ -497,7 +468,6 @@ const getAuth = function ( isLoggedIn, getToken, getActorId, - getUserActorId, getUserInfo, logout, register, diff --git a/src/models/api-key.ts b/src/models/api-key.ts index 9c338dcfd..f41a04d21 100644 --- a/src/models/api-key.ts +++ b/src/models/api-key.ts @@ -138,7 +138,7 @@ const getApiKeysModel = function ( mergePineOptions( { $filter: { - is_of__actor: await sdkInstance.auth.getUserActorId(), + is_of__actor: await sdkInstance.auth.getActorId(), // the only way to reason whether it's // a named user api key vs a deprecated user api key // is whether it has a name. diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index eed848402..044e64855 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -109,16 +109,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserActorId()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getUserActorId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); - describe.skip('balena.auth.register()', function () { beforeEach(async () => { await balena.auth.login({ @@ -224,16 +214,6 @@ describe('SDK authentication', function () { ); }); }); - - describe('balena.auth.getUserActorId()', () => { - it('should be rejected with an error', async function () { - const promise = balena.auth.getUserActorId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaNotLoggedIn', - ); - }); - }); }); describe('when logged in with credentials', function () { @@ -277,14 +257,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserActorId()', () => { - it('should eventually be a user id', async () => { - const userId = await balena.auth.getUserActorId(); - expect(userId).to.be.a('number'); - expect(userId).to.be.greaterThan(0); - }); - }); - describe('balena.auth.logout()', () => { it('should logout the user', async () => { await balena.auth.logout(); @@ -333,16 +305,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserActorId()', () => { - it('should be rejected with an error', async () => { - const promise = balena.auth.getUserActorId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.logout()', function () { it('should logout the user', async () => { await balena.auth.logout(); @@ -399,16 +361,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserActorId()', () => { - it('should be rejected with an error', async () => { - const promise = balena.auth.getUserActorId(); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'message', - 'The authentication credentials in use are not of a user', - ); - }); - }); - describe('balena.auth.logout()', function () { it('should logout the user', async () => { await balena.auth.logout(); @@ -465,14 +417,6 @@ describe('SDK authentication', function () { }); }); - describe('balena.auth.getUserActorId()', () => { - it('should eventually be a user id', async () => { - const userId = await balena.auth.getUserActorId(); - expect(userId).to.be.a('number'); - expect(userId).to.be.greaterThan(0); - }); - }); - describe('balena.auth.logout()', function () { it('should logout the user', async () => { await balena.auth.logout(); diff --git a/tests/integration/models/api-key.spec.ts b/tests/integration/models/api-key.spec.ts index 512066521..b2358405d 100644 --- a/tests/integration/models/api-key.spec.ts +++ b/tests/integration/models/api-key.spec.ts @@ -61,7 +61,7 @@ describe('API Key model', function () { it('should retrieve an empty array', async function () { const apiKeys = await balena.models.apiKey.getAll({ $filter: { - is_of__actor: await balena.auth.getUserActorId(), + is_of__actor: await balena.auth.getActorId(), name: { $ne: null }, }, }); @@ -81,7 +81,7 @@ describe('API Key model', function () { it('should be able to retrieve all api keys created', async function () { const apiKeys = await balena.models.apiKey.getAll({ $filter: { - is_of__actor: await balena.auth.getUserActorId(), + is_of__actor: await balena.auth.getActorId(), name: { $ne: null }, }, }); diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index fda0fd404..6a335c710 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -137,7 +137,7 @@ export async function resetUser() { // only delete named user api keys options: { $filter: { - is_of__actor: await balena.auth.getUserActorId(), + is_of__actor: await balena.auth.getActorId(), name: { $ne: null, }, From 447aa40350186467667d0f534b8fcb163ef8dfcd Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Thu, 10 Aug 2023 15:07:15 +0300 Subject: [PATCH 9/9] **BREAKING**: Remove the device-type.json state & name normalization We should not have any code any more relying on these. Change-type: major --- src/models/config.ts | 23 ++------------- tests/integration/models/config.spec.ts | 37 ++----------------------- tests/integration/setup.ts | 5 +++- 3 files changed, 9 insertions(+), 56 deletions(-) diff --git a/src/models/config.ts b/src/models/config.ts index dc107a652..d394d7d1c 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -73,28 +73,11 @@ const getConfigModel = function ( const { apiUrl } = opts; const normalizeDeviceTypes = ( - deviceTypes: DeviceTypeJson.DeviceType[], // Patch device types to be marked as ALPHA and BETA instead + deviceTypes: DeviceTypeJson.DeviceType[], ): DeviceTypeJson.DeviceType[] => - // of PREVIEW and EXPERIMENTAL, respectively. - // This logic is literally copy and pasted from balena UI, but - // there are plans to move this to `resin-device-types` so it - // should be a matter of time for this to be removed. deviceTypes.map(function (deviceType) { - // TODO: Drop in the next major the `deviceType.name.replace`s - if (deviceType.state === 'DISCONTINUED') { - deviceType.name = deviceType.name.replace( - /(\(PREVIEW|EXPERIMENTAL\))/, - '(DISCONTINUED)', - ); - } - if (deviceType.state === 'PREVIEW') { - deviceType.state = 'ALPHA'; - deviceType.name = deviceType.name.replace('(PREVIEW)', '(ALPHA)'); - } - if (deviceType.state === 'EXPERIMENTAL') { - deviceType.state = 'NEW'; - deviceType.name = deviceType.name.replace('(EXPERIMENTAL)', '(NEW)'); - } + // Remove the device-type.json instructions to enforce + // users to use the contract based ones. delete deviceType.instructions; return deviceType; }); diff --git a/tests/integration/models/config.spec.ts b/tests/integration/models/config.spec.ts index cf3181d58..687a7d2d8 100644 --- a/tests/integration/models/config.spec.ts +++ b/tests/integration/models/config.spec.ts @@ -19,47 +19,14 @@ const expectDeviceTypeArray = function ( } }; -const REPLACED_STATES = ['PREVIEW', 'EXPERIMENTAL']; - -const REPLACED_NAME_SUFFIXES = ['(PREVIEW)', '(EXPERIMENTAL)', '(BETA)']; - type ConfigContext = Mocha.Context & { deviceTypes: BalenaSdk.DeviceTypeJson.DeviceType[]; }; const itNormalizesDeviceTypes = function () { - it('changes old device type states', function (this: Mocha.Context) { - for (const deviceType of (this as ConfigContext).deviceTypes) { - expect(deviceType.state).to.satisfy((dtState: string) => - _.every(REPLACED_STATES, (replacedState) => dtState !== replacedState), - ); - } - }); - - it('changes old device type name suffixes', function (this: Mocha.Context) { - for (const deviceType of (this as ConfigContext).deviceTypes) { - expect(deviceType.name).to.satisfy((dtName: string) => - _.every( - REPLACED_NAME_SUFFIXES, - (replacedSuffix) => !_.endsWith(dtName, replacedSuffix), - ), - ); - } - }); - - it('properly replaces the names of device types with old states', function (this: Mocha.Context) { + it('should not have an `instructions` field', function (this: Mocha.Context) { for (const deviceType of (this as ConfigContext).deviceTypes) { - if (deviceType.state === 'PREVIEW') { - expect(deviceType.name).to.satisfy((dtName: string) => - _.endsWith(dtName, '(ALPHA)'), - ); - } - - if (deviceType.state === 'BETA') { - expect(deviceType.name).to.satisfy((dtName: string) => - _.endsWith(dtName, '(NEW)'), - ); - } + expect(deviceType).to.not.have.property('instructions'); } }); }; diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index 6a335c710..ff64cd6e5 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -179,7 +179,10 @@ export function givenLoggedInUserWithApiKey(beforeFn: Mocha.HookFunction) { afterFn(() => resetUser()); } -export function givenLoggedInUser(beforeFn: Mocha.HookFunction, forceRelogin = false) { +export function givenLoggedInUser( + beforeFn: Mocha.HookFunction, + forceRelogin = false, +) { beforeFn(async () => { await balena.auth.login({ email: credentials.email,