Skip to content

Commit

Permalink
feat: add option to provide role for call token (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
szuperaz authored May 21, 2024
1 parent 6c5bddb commit befe685
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 42 deletions.
19 changes: 16 additions & 3 deletions __tests__/create-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,28 @@ describe('creating tokens', () => {
});

it('with call IDs provided', () => {
const call_cids = ['default:call1', 'livestream:call2'];
const token = client.createCallToken(userId, call_cids);
const decodedToken = jwt.verify(token, secret) as any;

expect(decodedToken.user_id).toEqual(userId);
expect(decodedToken.call_cids).toEqual(call_cids);
expect(decodedToken.iat).toBeDefined();
expect(decodedToken.exp).toBeDefined();
});

it('with call IDs and role provided', () => {
const call_cids = ['default:call1', 'livestream:call2'];
const token = client.createCallToken(
userId,
{ user_id: userId, role: 'admin' },
call_cids,
undefined,
undefined,
);
const decodedToken = jwt.verify(token, secret) as any;

expect(decodedToken.call_cids).toEqual(call_cids);
expect(decodedToken.role).toEqual('admin');
expect(decodedToken.user_id).toEqual(userId);
expect(decodedToken.iat).toBeDefined();
expect(decodedToken.exp).toBeDefined();
});
});
45 changes: 22 additions & 23 deletions src/StreamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import { JWTServerToken, JWTUserToken } from './utils/create-token';
import crypto from 'crypto';
import { CallTokenPayload, UserTokenPayload } from './types';

export interface StreamClientOptions {
timeout?: number;
Expand Down Expand Up @@ -141,24 +142,20 @@ export class StreamClient {
iat = Math.round(Date.now() / 1000),
call_cids?: string[],
) {
const extra: { exp?: number; iat?: number; call_cids?: string[] } = {};

if (exp) {
extra.exp = exp;
}

if (iat) {
extra.iat = iat;
}
const payload: UserTokenPayload = {
user_id: userID,
exp,
iat,
};

if (call_cids) {
console.warn(
`Use createCallToken method for creating call tokens, the "call_cids" param will be removed from the createToken method with version 0.2.0`,
);
extra.call_cids = call_cids;
payload.call_cids = call_cids;
}

return JWTUserToken(this.secret, userID, extra);
return JWTUserToken(this.secret, payload);
}

/**
Expand All @@ -170,24 +167,26 @@ export class StreamClient {
* @returns
*/
createCallToken(
userID: string,
userIdOrObject: string | { user_id: string; role?: string },
call_cids: string[],
exp = Math.round(new Date().getTime() / 1000) + 60 * 60,
iat = Math.round(Date.now() / 1000),
) {
const extra: { exp?: number; iat?: number; call_cids?: string[] } = {};

if (exp) {
extra.exp = exp;
}

if (iat) {
extra.iat = iat;
const payload: CallTokenPayload = {
exp,
iat,
call_cids,
user_id:
typeof userIdOrObject === 'string'
? userIdOrObject
: userIdOrObject.user_id,
};

if (typeof userIdOrObject === 'object' && userIdOrObject.role) {
payload.role = userIdOrObject.role;
}

extra.call_cids = call_cids;

return JWTUserToken(this.secret, userID, extra);
return JWTUserToken(this.secret, payload);
}

createDevice = (createDeviceRequest: CreateDeviceRequest) => {
Expand Down
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
export type OmitTypeId<T> = Omit<T, 'type' | 'id' | 'connection_id'>;

interface BaseTokenPayload {
user_id: string;
exp: number;
iat: number;
call_cids?: string[];
}

export type UserTokenPayload = BaseTokenPayload;

export type CallTokenPayload = BaseTokenPayload & {
call_cids: string[];
role?: string;
};
21 changes: 5 additions & 16 deletions src/utils/create-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,19 @@ import jwt, { Secret, SignOptions } from 'jsonwebtoken';

export function JWTUserToken(
apiSecret: Secret,
userId: string,
extraData = {},
jwtOptions: SignOptions = {},
payload: { user_id: string; exp: number; iat: number; call_cids?: string[] },
) {
if (typeof userId !== 'string') {
throw new TypeError('userId should be a string');
}

const payload: { user_id: string } & any = {
user_id: userId,
...extraData,
};

// make sure we return a clear error when jwt is shimmed (ie. browser build)
if (jwt == null || jwt.sign == null) {
throw Error(
`Unable to find jwt crypto, if you are getting this error is probably because you are trying to generate tokens on browser or React Native (or other environment where crypto functions are not available). Please Note: token should only be generated server-side.`,
);
}

const opts: SignOptions = Object.assign(
{ algorithm: 'HS256', noTimestamp: true },
jwtOptions,
);
const opts: SignOptions = Object.assign({
algorithm: 'HS256',
noTimestamp: true,
});

if (payload.iat) {
opts.noTimestamp = false;
Expand Down

0 comments on commit befe685

Please sign in to comment.