Skip to content

Commit

Permalink
feat(stable): add support for sessions API (#1177)
Browse files Browse the repository at this point in the history
  • Loading branch information
polomani authored Dec 12, 2024
1 parent 61b4215 commit 28b0fdd
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 0 deletions.
61 changes: 61 additions & 0 deletions packages/stable/src/__tests__/api/sessions.int.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { SessionFilter } from 'stable/src/api/sessions/types';
import { beforeAll, describe, expect, test } from 'vitest';
import type CogniteClient from '../../cogniteClient';
import { setupLoggedInClient } from '../testUtils';

describe('Sessions integration test', () => {
let testSessionId: number | undefined = undefined;
let client: CogniteClient;

beforeAll(async () => {
client = setupLoggedInClient();
});

test('create', async () => {
const clientSecret = process.env.COGNITE_CLIENT_SECRET || '';
const clientId = process.env.COGNITE_CLIENT_ID || '';
expect(clientSecret).toBeTruthy();
expect(clientId).toBeTruthy();
const result = await client.sessions.create([
{
clientSecret,
clientId,
},
]);

expect(result).toHaveLength(1);
expect(typeof result[0].id).toBe('number');
testSessionId = result[0].id;

expect(result[0].status).toBe('READY');
expect(typeof result[0].nonce).toBe('string');
expect(result[0].type).toBe('CLIENT_CREDENTIALS');
expect(result[0].clientId).toBe(clientId);
});

test('list', async () => {
expect(testSessionId).toBeTruthy();
const filter: SessionFilter = { status: 'READY' };
const items = await client.sessions.list({ filter }).autoPagingToArray();

expect(items.length).toBeGreaterThan(0);
const foundSession = items.find((session) => session.id === testSessionId);
expect(foundSession).toBeDefined();
expect(foundSession?.creationTime).toBeLessThan(Date.now());
expect(foundSession?.expirationTime).toBeGreaterThan(Date.now());
});

test('retrieve', async () => {
expect(testSessionId).toBeTruthy();
const result = await client.sessions.retrieve([{ id: testSessionId }]);
expect(result).toHaveLength(1);
expect(result[0].id).toBe(testSessionId);
});

test('revoke', async () => {
expect(testSessionId).toBeTruthy();
const result = await client.sessions.revoke([{ id: testSessionId }]);
expect(result).toHaveLength(1);
expect(result[0].status).toBe('REVOKED');
});
});
64 changes: 64 additions & 0 deletions packages/stable/src/__tests__/api/sessions.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import nock from 'nock';
import { beforeEach, describe, expect, test } from 'vitest';
import type CogniteClient from '../../cogniteClient';
import { mockBaseUrl, setupMockableClient } from '../testUtils';

describe('Sessions unit test', () => {
let client: CogniteClient;

beforeEach(() => {
nock.cleanAll();
client = setupMockableClient();
});

test('create', async () => {
const createItem = { tokenExchange: true as const };
const response = {
items: [{ id: 1, status: 'ACTIVE', nonce: 'abc' }],
};

nock(mockBaseUrl)
.post(/\/sessions/)
.reply(200, response);

const result = await client.sessions.create([createItem]);
expect(result).toEqual(response.items);
});

test('list', async () => {
const filter = { status: 'ACTIVE' as const };
const response = { items: [{ id: 1, status: 'ACTIVE' }] };

nock(mockBaseUrl)
.get(/\/sessions/)
.query(filter)
.reply(200, response);

const items = await client.sessions.list({ filter }).autoPagingToArray();
expect(items).toEqual(response.items);
});

test('retrieve', async () => {
const ids = [{ id: 1 }];
const response = { items: [{ id: 1, status: 'ACTIVE' }] };

nock(mockBaseUrl)
.post(/\/sessions\/byids/)
.reply(200, response);

const result = await client.sessions.retrieve(ids);
expect(result).toEqual(response.items);
});

test('revoke', async () => {
const ids = [{ id: 1 }];
const response = { items: [{ id: 1, status: 'REVOKED' }] };

nock(mockBaseUrl)
.post(/\/sessions\/revoke/)
.reply(200, response);

const result = await client.sessions.revoke(ids);
expect(result).toEqual(response.items);
});
});
72 changes: 72 additions & 0 deletions packages/stable/src/api/sessions/sessionsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
BaseResourceAPI,
type InternalId,
type ItemsResponse,
type ItemsWrapper,
} from '@cognite/sdk-core';
import type {
Session,
SessionCreate,
SessionCreateResultItem,
SessionFilterQuery,
} from './types';

export class SessionsApi extends BaseResourceAPI<Session> {
/**
* [Create a session](https://api-docs.cognite.com/20230101/tag/Sessions/operation/createSessions)
*
* ```js
* client.sessions.create([{ clientId: 'client-id', clientSecret: 'client-secret' }])
* ```
* */
public create = async (
items: SessionCreate[]
): Promise<SessionCreateResultItem[]> => {
const response = await this.post<ItemsWrapper<SessionCreateResultItem[]>>(
this.url(),
{ data: { items } }
);
return response.data.items;
};

/**
* [List sessions](https://api-docs.cognite.com/20230101/tag/Sessions/operation/listSessions)
*
* ```js
* client.sessions.list({ filter: { status: 'ACTIVE' } })
* ```
*/
public list = (query?: SessionFilterQuery) => {
const { filter, ...rest } = query || {};
return this.listEndpoint(this.callListEndpointWithGet, {
...rest,
...filter,
});
};

/**
* [Retrieve sessions with given IDs](https://api-docs.cognite.com/20230101/tag/Sessions/operation/getSessionsByIds)
*
* ```js
* client.sessions.retrieve([{ id: 1 }])
* ```
*/
public retrieve = (ids: InternalId[]) => {
return this.retrieveEndpoint(ids);
};

/**
* [Revoke access to a session](https://api-docs.cognite.com/20230101/tag/Sessions/operation/revokeSessions)
*
* ```js
* client.sessions.revoke([{ id: 1 }])
* ```
*/
async revoke(ids: InternalId[]) {
const url = this.url('revoke');
const res = await this.post<ItemsResponse<Session>>(url, {
data: { items: ids },
});
return res.data.items;
}
}
107 changes: 107 additions & 0 deletions packages/stable/src/api/sessions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { EpochTimestamp, FilterQuery } from '../../types';

export type SessionStatus =
| 'READY'
| 'ACTIVE'
| 'CANCELLED'
| 'EXPIRED'
| 'REVOKED'
| 'ACCESS_LOST';

export const SessionStatus = {
READY: 'READY' as SessionStatus,
ACTIVE: 'ACTIVE' as SessionStatus,
CANCELLED: 'CANCELLED' as SessionStatus,
EXPIRED: 'EXPIRED' as SessionStatus,
REVOKED: 'REVOKED' as SessionStatus,
ACCESS_LOST: 'ACCESS_LOST' as SessionStatus,
};

export type SessionType =
| 'CLIENT_CREDENTIALS'
| 'TOKEN_EXCHANGE'
| 'ONESHOT_TOKEN_EXCHANGE';

export const SessionType = {
CLIENT_CREDENTIALS: 'CLIENT_CREDENTIALS' as SessionType,
TOKEN_EXCHANGE: 'TOKEN_EXCHANGE' as SessionType,
ONESHOT_TOKEN_EXCHANGE: 'ONESHOT_TOKEN_EXCHANGE' as SessionType,
};

/**
* A response with the ID, nonce and other information related to the session. The nonce
is short-lived and should be immediately passed to the endpoint that will use
the session.
*/
export interface SessionCreateResultItem {
/** ID of the session */
id: number;
/** Client ID in identity provider. Returned only if the session was created using client credentials */
clientId?: string;
/** Nonce to be passed to the internal service that will bind the session */
nonce: string;
/** Current status of the session */
status: SessionStatus;
/** Values reserved for future use */
type?: SessionType;
}

/**
* Credentials for a session using client credentials from an identity provider.
*/
export interface SessionWithClientCredentialsCreate {
/** Client ID in identity provider */
clientId: string;
/** Client secret in identity provider */
clientSecret: string;
}
/**
* Credentials for a session using one-shot token exchange to reuse the user's credentials.
One-shot sessions are short-lived sessions that are not refreshed and do not require support for token exchange from the identity provider.
*/
export interface SessionWithOneshotTokenExchangeCreate {
/** Use one-shot token exchange for the session. Must be `true`. */
oneshotTokenExchange: true;
}
/**
* Credentials for a session using token exchange to reuse the user's credentials.
*/
export interface SessionWithTokenExchangeCreate {
/** Use token exchange for the session. Must be `true`. */
tokenExchange: true;
}

export type SessionCreate =
| SessionWithClientCredentialsCreate
| SessionWithTokenExchangeCreate
| SessionWithOneshotTokenExchangeCreate;

export interface SessionFilter {
/** Current status of the session */
status?:
| 'READY'
| 'ACTIVE'
| 'CANCELLED'
| 'EXPIRED'
| 'REVOKED'
| 'ACCESS_LOST';
}

export interface SessionFilterQuery extends FilterQuery {
filter?: SessionFilter;
}

export interface Session {
/** ID of the session */
id?: number;
/** Client ID in identity provider. Returned only if the session was created using client credentials */
clientId?: string;
/** Session creation time, in milliseconds since 1970 */
creationTime?: EpochTimestamp;
/** Session expiry time, in milliseconds since 1970. This value is updated on refreshing a token */
expirationTime?: EpochTimestamp;
/** Current status of the session */
status?: SessionStatus;
/** Values reserved for future use */
type?: SessionType;
}
6 changes: 6 additions & 0 deletions packages/stable/src/cogniteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { RelationshipsApi } from './api/relationships/relationshipsApi';
import { SecurityCategoriesAPI } from './api/securityCategories/securityCategoriesApi';
import { SequencesAPI } from './api/sequences/sequencesApi';
import { ServiceAccountsAPI } from './api/serviceAccounts/serviceAccountsApi';
import { SessionsApi } from './api/sessions/sessionsApi';
import { SpacesAPI } from './api/spaces/spacesApi';
import {
TemplateGraphQlApi,
Expand Down Expand Up @@ -126,6 +127,9 @@ export default class CogniteClient extends BaseCogniteClient {
public get profiles() {
return accessApi(this.profilesApi);
}
public get sessions() {
return accessApi(this.sessionsApi);
}
public get templates() {
return {
groups: accessApi(this.apiFactory(TemplateGroupsApi, 'templategroups')),
Expand Down Expand Up @@ -215,6 +219,7 @@ export default class CogniteClient extends BaseCogniteClient {
private viewsApi?: ViewsAPI;
private spacesApi?: SpacesAPI;
private dataModelsApi?: DataModelsAPI;
private sessionsApi?: SessionsApi;

protected get version() {
return version;
Expand Down Expand Up @@ -275,6 +280,7 @@ export default class CogniteClient extends BaseCogniteClient {
this.viewsApi = this.apiFactory(ViewsAPI, 'models/views');
this.spacesApi = this.apiFactory(SpacesAPI, 'models/spaces');
this.dataModelsApi = this.apiFactory(DataModelsAPI, 'models/datamodels');
this.sessionsApi = this.apiFactory(SessionsApi, 'sessions');
}

static urlEncodeExternalId(externalId: string): string {
Expand Down
2 changes: 2 additions & 0 deletions packages/stable/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3580,3 +3580,5 @@ export {
ViewDefinitionProperty,
ViewPropertyDefinition,
} from './api/models/types.gen';

export * from './api/sessions/types';

0 comments on commit 28b0fdd

Please sign in to comment.