Skip to content

Commit 836739d

Browse files
authored
Merge pull request #774 from ozsay/accounts-server-custom-tokens-creator
[discuss] accounts-server token creator
2 parents c0a7905 + cf4554f commit 836739d

File tree

7 files changed

+131
-16
lines changed

7 files changed

+131
-16
lines changed

packages/database-mongo/__tests__/index.ts

+27
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,33 @@ describe('Mongo', () => {
679679
expect(ret!.updatedAt).toBeTruthy();
680680
expect(ret!.createdAt).not.toEqual(ret!.updatedAt);
681681
});
682+
683+
it('should update session with token', async () => {
684+
const token = generateRandomToken();
685+
const token2 = generateRandomToken();
686+
const sessionId = await databaseTests.database.createSession(session.userId, token, {
687+
ip: session.ip,
688+
userAgent: session.userAgent,
689+
});
690+
await delay(10);
691+
await databaseTests.database.updateSession(
692+
sessionId,
693+
{
694+
ip: 'new ip',
695+
userAgent: 'new user agent',
696+
},
697+
token2
698+
);
699+
const ret = await databaseTests.database.findSessionById(sessionId);
700+
expect(ret!.userId).toEqual(session.userId);
701+
expect(ret!.ip).toEqual('new ip');
702+
expect(ret!.userAgent).toEqual('new user agent');
703+
expect(ret!.valid).toEqual(true);
704+
expect(ret!.token).toEqual(token2);
705+
expect(ret!.createdAt).toBeTruthy();
706+
expect(ret!.updatedAt).toBeTruthy();
707+
expect(ret!.createdAt).not.toEqual(ret!.updatedAt);
708+
});
682709
});
683710

684711
describe('invalidateSession', () => {

packages/database-mongo/src/mongo.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -319,18 +319,25 @@ export class Mongo implements DatabaseInterface {
319319
return ret.ops[0]._id.toString();
320320
}
321321

322-
public async updateSession(sessionId: string, connection: ConnectionInformations): Promise<void> {
322+
public async updateSession(
323+
sessionId: string,
324+
connection: ConnectionInformations,
325+
newToken?: string
326+
): Promise<void> {
327+
const updateClause = {
328+
$set: {
329+
userAgent: connection.userAgent,
330+
ip: connection.ip,
331+
[this.options.timestamps.updatedAt]: this.options.dateProvider(),
332+
},
333+
};
334+
335+
if (newToken) {
336+
updateClause.$set.token = newToken;
337+
}
338+
323339
const _id = this.options.convertSessionIdToMongoObjectId ? toMongoID(sessionId) : sessionId;
324-
await this.sessionCollection.updateOne(
325-
{ _id },
326-
{
327-
$set: {
328-
userAgent: connection.userAgent,
329-
ip: connection.ip,
330-
[this.options.timestamps.updatedAt]: this.options.dateProvider(),
331-
},
332-
}
333-
);
340+
await this.sessionCollection.updateOne({ _id }, updateClause);
334341
}
335342

336343
public async invalidateSession(sessionId: string): Promise<void> {

packages/server/__tests__/account-server.ts

+54-1
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,60 @@ describe('AccountsServer', () => {
540540
refreshToken: 'newRefreshToken',
541541
});
542542
const res = await accountsServer.refreshTokens(accessToken, refreshToken, 'ip', 'user agent');
543-
expect(updateSession.mock.calls[0]).toEqual(['456', { ip: 'ip', userAgent: 'user agent' }]);
543+
expect(updateSession.mock.calls[0]).toEqual([
544+
'456',
545+
{ ip: 'ip', userAgent: 'user agent' },
546+
undefined,
547+
]);
548+
expect((res as any).user).toEqual({
549+
userId: '123',
550+
username: 'username',
551+
});
552+
});
553+
554+
it('updates session and returns new tokens and user with new session token', async () => {
555+
const updateSession = jest.fn(() => Promise.resolve());
556+
const user = {
557+
userId: '123',
558+
username: 'username',
559+
};
560+
const accountsServer = new AccountsServer(
561+
{
562+
db: {
563+
findSessionByToken: () =>
564+
Promise.resolve({
565+
id: '456',
566+
valid: true,
567+
userId: '123',
568+
}),
569+
findUserById: () => Promise.resolve(user),
570+
updateSession,
571+
} as any,
572+
tokenSecret: 'secret1',
573+
createNewSessionTokenOnRefresh: true,
574+
tokenCreator: {
575+
createToken: async () => {
576+
return '123';
577+
},
578+
},
579+
},
580+
{}
581+
);
582+
const { accessToken, refreshToken } = accountsServer.createTokens({
583+
token: '456',
584+
userId: user.userId,
585+
});
586+
accountsServer.createTokens = () => ({
587+
accessToken: 'newAccessToken',
588+
refreshToken: 'newRefreshToken',
589+
});
590+
591+
const res = await accountsServer.refreshTokens(accessToken, refreshToken, 'ip', 'user agent');
592+
expect(updateSession.mock.calls[0]).toEqual([
593+
'456',
594+
{ ip: 'ip', userAgent: 'user agent' },
595+
'123',
596+
]);
544597
expect((res as any).user).toEqual({
545598
userId: '123',
546599
username: 'username',

packages/server/src/accounts-server.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const defaultOptions = {
3737
userObjectSanitizer: (user: User) => user,
3838
sendMail,
3939
siteUrl: 'http://localhost:3000',
40+
createNewSessionTokenOnRefresh: false,
4041
};
4142

4243
export class AccountsServer {
@@ -138,7 +139,7 @@ Please change it with a strong random token.`);
138139
*/
139140
public async loginWithUser(user: User, infos: ConnectionInformations): Promise<LoginResult> {
140141
const { ip, userAgent } = infos;
141-
const token = generateRandomToken();
142+
const token = await this.createSessionToken(user);
142143
const sessionId = await this.db.createSession(user.id, token, {
143144
ip,
144145
userAgent,
@@ -300,8 +301,14 @@ Please change it with a strong random token.`);
300301
if (!user) {
301302
throw new Error('User not found');
302303
}
303-
const tokens = this.createTokens({ token: sessionToken, userId: user.id });
304-
await this.db.updateSession(session.id, { ip, userAgent });
304+
305+
let newToken;
306+
if (this.options.createNewSessionTokenOnRefresh) {
307+
newToken = await this.createSessionToken(user);
308+
}
309+
310+
const tokens = this.createTokens({ token: newToken || sessionToken, userId: user.id });
311+
await this.db.updateSession(session.id, { ip, userAgent }, newToken);
305312

306313
const result = {
307314
sessionId: session.id,
@@ -515,6 +522,12 @@ Please change it with a strong random token.`);
515522
const siteUrl = this.options.siteUrl;
516523
return `${siteUrl}/${pathFragment}/${token}`;
517524
}
525+
526+
private async createSessionToken(user: User): Promise<string> {
527+
return this.options.tokenCreator
528+
? this.options.tokenCreator.createToken(user)
529+
: generateRandomToken();
530+
}
518531
}
519532

520533
export default AccountsServer;

packages/server/src/types/accounts-server-options.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UserObjectSanitizerFunction } from './user-object-sanitizer-function';
55
import { ResumeSessionValidator } from './resume-session-validator';
66
import { PrepareMailFunction } from './prepare-mail-function';
77
import { SendMailType } from './send-mail-type';
8+
import { TokenCreator } from './token-creator';
89

910
export interface AccountsServerOptions {
1011
/**
@@ -24,4 +25,9 @@ export interface AccountsServerOptions {
2425
siteUrl?: string;
2526
prepareMail?: PrepareMailFunction;
2627
sendMail?: SendMailType;
28+
tokenCreator?: TokenCreator;
29+
/**
30+
* Creates a new session token each time a user refreshes his access token
31+
*/
32+
createNewSessionTokenOnRefresh?: boolean;
2733
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { User } from '@accounts/types';
2+
3+
export interface TokenCreator {
4+
createToken(user: User): Promise<string>;
5+
}

packages/types/src/types/database-interface.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ export interface DatabaseInterfaceSessions {
7070
extraData?: object
7171
): Promise<string>;
7272

73-
updateSession(sessionId: string, connection: ConnectionInformations): Promise<void>;
73+
updateSession(
74+
sessionId: string,
75+
connection: ConnectionInformations,
76+
newToken?: string
77+
): Promise<void>;
7478

7579
invalidateSession(sessionId: string): Promise<void>;
7680

0 commit comments

Comments
 (0)