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/DOCUMENTATION.md b/DOCUMENTATION.md
index 448b5786c..407f615da 100644
--- a/DOCUMENTATION.md
+++ b/DOCUMENTATION.md
@@ -429,9 +429,8 @@ const sdk = fromSharedOptions();
* [.loginWithToken(authToken)](#balena.auth.loginWithToken) ⇒ Promise
* [.isLoggedIn()](#balena.auth.isLoggedIn) ⇒ Promise
* [.getToken()](#balena.auth.getToken) ⇒ Promise
- * [.getUserId()](#balena.auth.getUserId) ⇒ Promise
- * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise
- * [.getEmail()](#balena.auth.getEmail) ⇒ Promise
+ * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise
+ * [.getActorId()](#balena.auth.getActorId) ⇒ Promise
* [.logout()](#balena.auth.logout) ⇒ Promise
* [.register(credentials)](#balena.auth.register) ⇒ Promise
* [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise
@@ -6483,9 +6482,8 @@ 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
- * [.getUserId()](#balena.auth.getUserId) ⇒ Promise
- * [.getUserActorId()](#balena.auth.getUserActorId) ⇒ Promise
- * [.getEmail()](#balena.auth.getEmail) ⇒ Promise
+ * [.getUserInfo()](#balena.auth.getUserInfo) ⇒ Promise
+ * [.getActorId()](#balena.auth.getActorId) ⇒ Promise
* [.logout()](#balena.auth.logout) ⇒ Promise
* [.register(credentials)](#balena.auth.register) ⇒ Promise
* [.verifyEmail(verificationPayload)](#balena.auth.verifyEmail) ⇒ Promise
@@ -6630,19 +6628,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);
}
});
```
@@ -6740,49 +6738,34 @@ balena.auth.getToken().then(function(token) {
console.log(token);
});
```
-
+
-#### auth.getUserId() ⇒ Promise
+#### 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 id
+**Summary**: Get current logged in user's info
**Access**: public
-**Fulfil**: Number
- user id
+**Fulfil**: Object
- user info
**Example**
```js
-balena.auth.getUserId().then(function(userId) {
- console.log(userId);
+balena.auth.getUserInfo().then(function(userInfo) {
+ console.log(userInfo);
});
```
-
-
-#### 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.getEmail() ⇒ Promise
-This will only work if you used [login](#balena.auth.login) to log in.
+#### 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 user's email
+**Summary**: Get current logged in actor id
**Access**: public
-**Fulfil**: String
- user email
+**Fulfil**: Number
- actor id
**Example**
```js
-balena.auth.getEmail().then(function(email) {
- console.log(email);
+balena.auth.getActorId().then(function(actorId) {
+ console.log(actorId);
});
```
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",
diff --git a/src/auth.ts b/src/auth.ts
index 1440f7ffd..a47d440e6 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 { UserInfo, 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 (
@@ -286,75 +280,56 @@ const getAuth = function (
}
/**
- * @summary Get current logged in user's id
- * @name getUserId
+ * @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 {Number} - user id
+ * @fulfil {Object} - user info
* @returns {Promise}
*
* @example
- * balena.auth.getUserId().then(function(userId) {
- * console.log(userId);
+ * balena.auth.getUserInfo().then(function(userInfo) {
+ * console.log(userInfo);
* });
*/
- async function getUserId(): Promise {
- const { id } = await getUserDetails();
- return id;
- }
+ async function getUserInfo(): Promise {
+ const actor = await getActorDetails();
- /**
- * @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 { actor } = (await pine.get({
- resource: 'user',
- id: await getUserId(),
- options: {
- $select: 'actor',
- },
- }))!;
- return actor;
+ 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 email
- * @name getEmail
+ * @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} 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} - user email
+ * @fulfil {Number} - actor id
* @returns {Promise}
*
* @example
- * balena.auth.getEmail().then(function(email) {
- * console.log(email);
+ * balena.auth.getActorId().then(function(actorId) {
+ * console.log(actorId);
* });
*/
- async function getEmail(): Promise {
- const { email } = await getUserDetails();
- return email;
+ async function getActorId(): Promise {
+ return (await getActorDetails()).id;
}
/**
@@ -370,7 +345,7 @@ const getAuth = function (
* balena.auth.logout();
*/
function logout(): Promise {
- memoizedUserWhoami.clear();
+ memoizedActorWhoami.clear();
return auth.removeKey();
}
@@ -474,7 +449,7 @@ const getAuth = function (
*
*/
async function requestVerificationEmail() {
- const id = await getUserId();
+ const { id } = await getUserInfo();
await pine.patch({
resource: 'user',
id,
@@ -492,9 +467,8 @@ const getAuth = function (
loginWithToken,
isLoggedIn,
getToken,
- getUserId,
- getUserActorId,
- getEmail,
+ getActorId,
+ getUserInfo,
logout,
register,
verifyEmail,
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/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/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/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/src/models/device.ts b/src/models/device.ts
index 8f48a5022..e75dfb017 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;
},
/**
@@ -1370,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/src/types/auth.ts b/src/types/auth.ts
new file mode 100644
index 000000000..0f0dc257b
--- /dev/null
+++ b/src/types/auth.ts
@@ -0,0 +1,32 @@
+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;
+
+export interface UserInfo {
+ id: number;
+ username: string;
+ email: string | null;
+}
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,
diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts
index fb2653361..044e64855 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);
@@ -82,9 +89,9 @@ describe('SDK authentication', function () {
});
});
- describe('balena.auth.getEmail()', () => {
+ describe('balena.auth.getUserInfo()', () => {
it('should be rejected with an error', async function () {
- const promise = balena.auth.getEmail();
+ const promise = balena.auth.getUserInfo();
await expect(promise).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
@@ -92,19 +99,9 @@ describe('SDK authentication', function () {
});
});
- describe('balena.auth.getUserId()', () => {
+ describe('balena.auth.getActorId()', () => {
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();
+ const promise = balena.auth.getActorId();
await expect(promise).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
@@ -118,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})`,
@@ -198,19 +195,9 @@ 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.getUserId()', () => {
+ describe('balena.auth.getUserInfo()', () => {
it('should be rejected with an error', async function () {
- const promise = balena.auth.getUserId();
+ const promise = balena.auth.getUserInfo();
await expect(promise).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
@@ -218,9 +205,9 @@ describe('SDK authentication', function () {
});
});
- describe('balena.auth.getUserActorId()', () => {
+ describe('balena.auth.getActorId()', () => {
it('should be rejected with an error', async function () {
- const promise = balena.auth.getUserActorId();
+ const promise = balena.auth.getActorId();
await expect(promise).to.be.rejected.and.eventually.have.property(
'code',
'BalenaNotLoggedIn',
@@ -239,43 +226,103 @@ 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');
});
});
- 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();
+ 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();
+ 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();
+ describe('balena.auth.logout()', () => {
+ it('should logout the user', async () => {
+ await balena.auth.logout();
+ expect(await balena.auth.isLoggedIn()).to.be.false;
+ });
+ });
+ });
+
+ 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.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.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.logout()', () => {
+ 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 API key', function () {
- givenLoggedInUserWithApiKey(before);
+ describe('when logged in with an application API Key', function () {
+ givenLoggedInWithAnApplicationApiKey(before);
describe('balena.auth.isLoggedIn()', () => {
it('should eventually be true', async function () {
@@ -284,28 +331,87 @@ 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 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 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 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 eventually be a user id', async () => {
- const userId = await balena.auth.getUserId();
+ 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();
+ 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()', () => {
+ it('should eventually be true', async function () {
+ expect(await balena.auth.isLoggedIn()).to.be.true;
+ });
+ });
+
+ describe('balena.auth.whoami()', () => {
+ 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');
+ });
+ });
+
+ 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.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);
});
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/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/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/models/organization-membership.spec.ts b/tests/integration/models/organization-membership.spec.ts
index 8c7a3deda..c72217079 100644
--- a/tests/integration/models/organization-membership.spec.ts
+++ b/tests/integration/models/organization-membership.spec.ts
@@ -43,8 +43,10 @@ describe('Organization Membership Model', function () {
let ctx: Mocha.Context;
before(async function () {
ctx = this;
- this.username = await balena.auth.whoami();
- this.userId = await balena.auth.getUserId();
+ const userInfoResult = await balena.auth.getUserInfo();
+ this.username = userInfoResult.username;
+ 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 2e7e7cdbc..ff64cd6e5 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,
},
@@ -179,7 +179,10 @@ 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 +192,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() {
@@ -207,7 +219,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',
@@ -349,6 +361,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,
@@ -459,7 +498,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(
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 }) =>