Skip to content

Commit

Permalink
refactor(oauth-server): Import code, convert to TS and update depende…
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigok committed Apr 21, 2023
1 parent b6614f8 commit ba09206
Show file tree
Hide file tree
Showing 28 changed files with 879 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { OAuthApps } from '@rocket.chat/models';
import { OAuthAccessTokens, OAuthApps, OAuthAuthCodes } from '@rocket.chat/models';
import type { IOAuthApps } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ui-contexts';

Expand Down Expand Up @@ -31,6 +31,9 @@ Meteor.methods<ServerMethods>({

await OAuthApps.deleteOne({ _id: applicationId });

await OAuthAccessTokens.deleteMany({ clientId: application.clientId });
await OAuthAuthCodes.deleteMany({ clientId: application.clientId });

return true;
},
});
31 changes: 11 additions & 20 deletions apps/meteor/app/oauth2-server-config/server/oauth/oauth2-server.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { WebApp } from 'meteor/webapp';
import { OAuth2Server } from 'meteor/rocketchat:oauth2-server';
import type { Request, Response } from 'express';
import type { IUser } from '@rocket.chat/core-typings';
import { OAuthApps, Users } from '@rocket.chat/models';
import { OAuthAccessTokens, Users } from '@rocket.chat/models';

import { API } from '../../../api/server';
import { OAuth2Server } from '../../../../server/oauth2-server/oauth';

const oauth2server = new OAuth2Server({
accessTokensCollectionName: 'rocketchat_oauth_access_tokens',
refreshTokensCollectionName: 'rocketchat_oauth_refresh_tokens',
authCodesCollectionName: 'rocketchat_oauth_auth_codes',
// TODO: Remove workaround. Used to pass meteor collection reference to a package
clientsCollection: new Mongo.Collection(OAuthApps.col.collectionName),
// If you're developing something related to oauth servers, you should change this to true
debug: false,
});

// https://github.com/RocketChat/rocketchat-oauth2-server/blob/e758fd7ef69348c7ceceabe241747a986c32d036/model.coffee#L27-L27
function getAccessToken(accessToken: string): any {
return oauth2server.oauth.model.AccessTokens.findOne({
accessToken,
});
async function getAccessToken(accessToken: string) {
return OAuthAccessTokens.findOneByAccessToken(accessToken);
}

export async function oAuth2ServerAuth(partialRequest: {
Expand All @@ -32,10 +24,10 @@ export async function oAuth2ServerAuth(partialRequest: {
const headerToken = partialRequest.headers.authorization?.replace('Bearer ', '');
const queryToken = partialRequest.query.access_token;

const accessToken = getAccessToken(headerToken || queryToken);
const accessToken = await getAccessToken(headerToken || queryToken);

// If there is no token available or the token has expired, return undefined
if (!accessToken || (accessToken.expires != null && accessToken.expires !== 0 && accessToken.expires < new Date())) {
if (!accessToken || (accessToken.expires != null && accessToken.expires < new Date())) {
return;
}

Expand All @@ -49,22 +41,21 @@ export async function oAuth2ServerAuth(partialRequest: {
}

oauth2server.app.disable('x-powered-by');
oauth2server.routes.disable('x-powered-by');

WebApp.connectHandlers.use(oauth2server.app);

oauth2server.routes.get('/oauth/userinfo', async function (req: Request, res: Response) {
oauth2server.app.get('/oauth/userinfo', async function (req: Request, res: Response) {
if (req.headers.authorization == null) {
return res.sendStatus(401).send('No token');
return res.status(401).send('No token');
}
const accessToken = req.headers.authorization.replace('Bearer ', '');
const token = getAccessToken(accessToken);
const token = await getAccessToken(accessToken);
if (token == null) {
return res.sendStatus(401).send('Invalid Token');
return res.status(401).send('Invalid Token');
}
const user = await Users.findOneById(token.userId);
if (user == null) {
return res.sendStatus(401).send('Invalid Token');
return res.status(401).send('Invalid Token');
}
return res.send({
sub: user._id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const AuthorizationFormPage = ({ oauthApp, redirectUri, user }: AuthorizationFor
<li>{t('core.access_your_basic_information')}</li>
</ul>
</Box>
<input type='hidden' name='token' value={token} />
<input type='hidden' name='access_token' value={token} />
<input type='hidden' name='client_id' value={oauthApp.clientId} />
<input type='hidden' name='redirect_uri' value={redirectUri} />
<input type='hidden' name='response_type' value='code' />
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@types/node": "^14.18.21",
"@types/node-rsa": "^1.1.1",
"@types/nodemailer": "^6.4.4",
"@types/oauth2-server": "^3.0.13",
"@types/parseurl": "^1.3.1",
"@types/photoswipe": "^4.1.2",
"@types/prometheus-gc-stats": "^0.6.2",
Expand Down Expand Up @@ -362,6 +363,7 @@
"node-gcm": "1.0.5",
"node-rsa": "^1.1.1",
"nodemailer": "^6.7.8",
"oauth2-server": "3.1.1",
"object-path": "^0.11.8",
"path": "^0.12.7",
"path-to-regexp": "^6.2.1",
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/server/models/OAuthAccessTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerModel } from '@rocket.chat/models';

import { db } from '../database/utils';
import { OAuthAccessTokensRaw } from './raw/OAuthAccessTokens';

registerModel('IOAuthAccessTokensModel', new OAuthAccessTokensRaw(db));
6 changes: 6 additions & 0 deletions apps/meteor/server/models/OAuthAuthCodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerModel } from '@rocket.chat/models';

import { db } from '../database/utils';
import { OAuthAuthCodesRaw } from './raw/OAuthAuthCodes';

registerModel('IOAuthAuthCodesModel', new OAuthAuthCodesRaw(db));
6 changes: 6 additions & 0 deletions apps/meteor/server/models/OAuthRefreshTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { registerModel } from '@rocket.chat/models';

import { db } from '../database/utils';
import { OAuthRefreshTokensRaw } from './raw/OAuthRefreshTokens';

registerModel('IOAuthRefreshTokensModel', new OAuthRefreshTokensRaw(db));
28 changes: 28 additions & 0 deletions apps/meteor/server/models/raw/OAuthAccessTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IOAuthAccessToken, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IOAuthAccessTokensModel } from '@rocket.chat/model-typings';
import type { Db, Collection, FindOptions, IndexDescription } from 'mongodb';

import { BaseRaw } from './BaseRaw';

export class OAuthAccessTokensRaw extends BaseRaw<IOAuthAccessToken> implements IOAuthAccessTokensModel {
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IOAuthAccessToken>>) {
super(db, 'oauth_access_tokens', trash);
}

modelIndexes(): IndexDescription[] {
return [
{ key: { accessToken: 1 } },
{ key: { refreshToken: 1 } },
{ key: { expires: 1 }, expireAfterSeconds: 60 * 60 * 24 * 30 },
{ key: { refreshTokenExpiresAt: 1 }, expireAfterSeconds: 60 * 60 * 24 * 30 },
];
}

findOneByAccessToken(accessToken: string, options?: FindOptions<IOAuthAccessToken>): Promise<IOAuthAccessToken | null> {
return this.findOne({ accessToken }, options);
}

findOneByRefreshToken(refreshToken: string, options?: FindOptions<IOAuthAccessToken>): Promise<IOAuthAccessToken | null> {
return this.findOne({ refreshToken }, options);
}
}
31 changes: 30 additions & 1 deletion apps/meteor/server/models/raw/OAuthApps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IOAuthApps, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IOAuthAppsModel } from '@rocket.chat/model-typings';
import type { Db, Collection } from 'mongodb';
import type { Db, Collection, FindOptions, IndexDescription } from 'mongodb';

import { BaseRaw } from './BaseRaw';

Expand All @@ -9,11 +9,40 @@ export class OAuthAppsRaw extends BaseRaw<IOAuthApps> implements IOAuthAppsModel
super(db, 'oauth_apps', trash);
}

modelIndexes(): IndexDescription[] {
return [{ key: { clientId: 1, clientSecret: 1 } }, { key: { appId: 1 } }];
}

findOneAuthAppByIdOrClientId(props: { clientId: string } | { appId: string } | { _id: string }): Promise<IOAuthApps | null> {
return this.findOne({
...('_id' in props && { _id: props._id }),
...('appId' in props && { _id: props.appId }),
...('clientId' in props && { clientId: props.clientId }),
});
}

findOneActiveByClientId(clientId: string, options?: FindOptions<IOAuthApps>): Promise<IOAuthApps | null> {
return this.findOne(
{
active: true,
clientId,
},
options,
);
}

findOneActiveByClientIdAndClientSecret(
clientId: string,
clientSecret: string,
options?: FindOptions<IOAuthApps>,
): Promise<IOAuthApps | null> {
return this.findOne(
{
active: true,
clientId,
clientSecret,
},
options,
);
}
}
19 changes: 19 additions & 0 deletions apps/meteor/server/models/raw/OAuthAuthCodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { IOAuthAuthCode, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IOAuthAuthCodesModel } from '@rocket.chat/model-typings';
import type { Db, Collection, FindOptions, IndexDescription } from 'mongodb';

import { BaseRaw } from './BaseRaw';

export class OAuthAuthCodesRaw extends BaseRaw<IOAuthAuthCode> implements IOAuthAuthCodesModel {
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IOAuthAuthCode>>) {
super(db, 'oauth_auth_codes', trash);
}

modelIndexes(): IndexDescription[] {
return [{ key: { authCode: 1 } }, { key: { expires: 1 }, expireAfterSeconds: 60 * 5 }];
}

findOneByAuthCode(authCode: string, options?: FindOptions<IOAuthAuthCode>): Promise<IOAuthAuthCode | null> {
return this.findOne({ authCode }, options);
}
}
19 changes: 19 additions & 0 deletions apps/meteor/server/models/raw/OAuthRefreshTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { IOAuthRefreshToken, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IOAuthRefreshTokensModel } from '@rocket.chat/model-typings';
import type { Db, Collection, FindOptions, IndexDescription } from 'mongodb';

import { BaseRaw } from './BaseRaw';

export class OAuthRefreshTokensRaw extends BaseRaw<IOAuthRefreshToken> implements IOAuthRefreshTokensModel {
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<IOAuthRefreshToken>>) {
super(db, 'oauth_refresh_tokens', trash);
}

modelIndexes(): IndexDescription[] {
return [{ key: { refreshToken: 1 } }, { key: { expires: 1 }, expireAfterSeconds: 60 * 60 * 24 * 30 }];
}

findOneByRefreshToken(refreshToken: string, options?: FindOptions<IOAuthRefreshToken>): Promise<IOAuthRefreshToken | null> {
return this.findOne({ refreshToken }, options);
}
}
3 changes: 3 additions & 0 deletions apps/meteor/server/models/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import './NotificationQueue';
import './Nps';
import './NpsVote';
import './OAuthApps';
import './OAuthAuthCodes';
import './OAuthAccessTokens';
import './OAuthRefreshTokens';
import './OEmbedCache';
import './PbxEvents';
import './PushToken';
Expand Down
Loading

0 comments on commit ba09206

Please sign in to comment.