Merge pull request #13 from hoppscotch/hotfix/subscriptions
Hotfix: fix subscription cookie issue
This commit is contained in:
@@ -6,7 +6,6 @@ POSTMARK_SERVER_TOKEN=************************************************"
|
|||||||
POSTMARK_SENDER_EMAIL=************************************************"
|
POSTMARK_SENDER_EMAIL=************************************************"
|
||||||
|
|
||||||
# Auth Tokens Config
|
# Auth Tokens Config
|
||||||
SIGNED_COOKIE_SECRET='add some secret here'
|
|
||||||
JWT_SECRET='add some secret here'
|
JWT_SECRET='add some secret here'
|
||||||
TOKEN_SALT_COMPLEXITY=10
|
TOKEN_SALT_COMPLEXITY=10
|
||||||
MAGIC_LINK_TOKEN_VALIDITY=3
|
MAGIC_LINK_TOKEN_VALIDITY=3
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ version: '3.0'
|
|||||||
services:
|
services:
|
||||||
local:
|
local:
|
||||||
build: .
|
build: .
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
command: [ "pnpm", "run", "start:dev" ]
|
command: [ "pnpm", "run", "start:dev" ]
|
||||||
environment:
|
environment:
|
||||||
- PRODUCTION=false
|
- PRODUCTION=false
|
||||||
|
|||||||
@@ -1,209 +0,0 @@
|
|||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "ReqType" AS ENUM ('REST', 'GQL');
|
|
||||||
|
|
||||||
-- CreateEnum
|
|
||||||
CREATE TYPE "TeamMemberRole" AS ENUM ('OWNER', 'VIEWER', 'EDITOR');
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Team" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "Team_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "TeamMember" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"role" "TeamMemberRole" NOT NULL,
|
|
||||||
"userUid" TEXT NOT NULL,
|
|
||||||
"teamID" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "TeamMember_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "TeamInvitation" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"teamID" TEXT NOT NULL,
|
|
||||||
"creatorUid" TEXT NOT NULL,
|
|
||||||
"inviteeEmail" TEXT NOT NULL,
|
|
||||||
"inviteeRole" "TeamMemberRole" NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "TeamInvitation_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "TeamCollection" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"parentID" TEXT,
|
|
||||||
"teamID" TEXT NOT NULL,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "TeamCollection_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "TeamRequest" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"collectionID" TEXT NOT NULL,
|
|
||||||
"teamID" TEXT NOT NULL,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"request" JSONB NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "TeamRequest_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Shortcode" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"request" JSONB NOT NULL,
|
|
||||||
"creatorUid" TEXT,
|
|
||||||
"createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "Shortcode_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "TeamEnvironment" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"teamID" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"variables" JSONB NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "TeamEnvironment_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "User" (
|
|
||||||
"uid" TEXT NOT NULL,
|
|
||||||
"displayName" TEXT,
|
|
||||||
"email" TEXT,
|
|
||||||
"photoURL" TEXT,
|
|
||||||
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
"refreshToken" TEXT,
|
|
||||||
"currentRESTSession" JSONB,
|
|
||||||
"currentGQLSession" JSONB,
|
|
||||||
"createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "User_pkey" PRIMARY KEY ("uid")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "Account" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userId" TEXT NOT NULL,
|
|
||||||
"provider" TEXT NOT NULL,
|
|
||||||
"providerAccountId" TEXT NOT NULL,
|
|
||||||
"providerRefreshToken" TEXT,
|
|
||||||
"providerAccessToken" TEXT,
|
|
||||||
"providerScope" TEXT,
|
|
||||||
"loggedIn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "VerificationToken" (
|
|
||||||
"deviceIdentifier" TEXT NOT NULL,
|
|
||||||
"token" TEXT NOT NULL,
|
|
||||||
"userUid" TEXT NOT NULL,
|
|
||||||
"expiresOn" TIMESTAMP(3) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserSettings" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userUid" TEXT NOT NULL,
|
|
||||||
"properties" JSONB NOT NULL,
|
|
||||||
"updatedOn" TIMESTAMP(3) NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "UserSettings_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserHistory" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userUid" TEXT NOT NULL,
|
|
||||||
"reqType" "ReqType" NOT NULL,
|
|
||||||
"request" JSONB NOT NULL,
|
|
||||||
"responseMetadata" JSONB NOT NULL,
|
|
||||||
"isStarred" BOOLEAN NOT NULL,
|
|
||||||
"executedOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
CONSTRAINT "UserHistory_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "UserEnvironment" (
|
|
||||||
"id" TEXT NOT NULL,
|
|
||||||
"userUid" TEXT NOT NULL,
|
|
||||||
"name" TEXT,
|
|
||||||
"variables" JSONB NOT NULL,
|
|
||||||
"isGlobal" BOOLEAN NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "UserEnvironment_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "TeamMember_teamID_userUid_key" ON "TeamMember"("teamID", "userUid");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE INDEX "TeamInvitation_teamID_idx" ON "TeamInvitation"("teamID");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "TeamInvitation_teamID_inviteeEmail_key" ON "TeamInvitation"("teamID", "inviteeEmail");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Shortcode_id_creatorUid_key" ON "Shortcode"("id", "creatorUid");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "VerificationToken_deviceIdentifier_token_key" ON "VerificationToken"("deviceIdentifier", "token");
|
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "UserSettings_userUid_key" ON "UserSettings"("userUid");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_teamID_fkey" FOREIGN KEY ("teamID") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamInvitation" ADD CONSTRAINT "TeamInvitation_teamID_fkey" FOREIGN KEY ("teamID") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamCollection" ADD CONSTRAINT "TeamCollection_parentID_fkey" FOREIGN KEY ("parentID") REFERENCES "TeamCollection"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamCollection" ADD CONSTRAINT "TeamCollection_teamID_fkey" FOREIGN KEY ("teamID") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamRequest" ADD CONSTRAINT "TeamRequest_collectionID_fkey" FOREIGN KEY ("collectionID") REFERENCES "TeamCollection"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamRequest" ADD CONSTRAINT "TeamRequest_teamID_fkey" FOREIGN KEY ("teamID") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "TeamEnvironment" ADD CONSTRAINT "TeamEnvironment_teamID_fkey" FOREIGN KEY ("teamID") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "VerificationToken" ADD CONSTRAINT "VerificationToken_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserSettings" ADD CONSTRAINT "UserSettings_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserHistory" ADD CONSTRAINT "UserHistory_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "UserEnvironment" ADD CONSTRAINT "UserEnvironment_userUid_fkey" FOREIGN KEY ("userUid") REFERENCES "User"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
@@ -7,6 +7,7 @@ import { AuthModule } from './auth/auth.module';
|
|||||||
import { UserSettingsModule } from './user-settings/user-settings.module';
|
import { UserSettingsModule } from './user-settings/user-settings.module';
|
||||||
import { UserEnvironmentsModule } from './user-environment/user-environments.module';
|
import { UserEnvironmentsModule } from './user-environment/user-environments.module';
|
||||||
import { UserHistoryModule } from './user-history/user-history.module';
|
import { UserHistoryModule } from './user-history/user-history.module';
|
||||||
|
import { subscriptionContextCookieParser } from './auth/helper';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,33 +23,21 @@ import { UserHistoryModule } from './user-history/user-history.module';
|
|||||||
subscriptions: {
|
subscriptions: {
|
||||||
'subscriptions-transport-ws': {
|
'subscriptions-transport-ws': {
|
||||||
path: '/graphql',
|
path: '/graphql',
|
||||||
onConnect: (connectionParams: any) => {
|
onConnect: (_, websocket) => {
|
||||||
|
const cookies = subscriptionContextCookieParser(
|
||||||
|
websocket.upgradeReq.headers.cookie,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
reqHeaders: Object.fromEntries(
|
headers: { ...websocket?.upgradeReq?.headers, cookies },
|
||||||
Object.entries(connectionParams).map(([k, v]) => [
|
|
||||||
k.toLowerCase(),
|
|
||||||
v,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: async ({ req, connection }) => {
|
context: ({ req, res, connection }) => ({
|
||||||
if (req) {
|
req,
|
||||||
return { reqHeaders: req.headers };
|
res,
|
||||||
} else {
|
connection,
|
||||||
return {
|
}),
|
||||||
// Lowercase the keys
|
|
||||||
reqHeaders: Object.fromEntries(
|
|
||||||
Object.entries(connection.context).map(([k, v]) => [
|
|
||||||
k.toLowerCase(),
|
|
||||||
v,
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
driver: ApolloDriver,
|
driver: ApolloDriver,
|
||||||
}),
|
}),
|
||||||
UserModule,
|
UserModule,
|
||||||
|
|||||||
@@ -41,16 +41,28 @@ export const authCookieHandler = (
|
|||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
maxAge: accessTokenValidity,
|
maxAge: accessTokenValidity,
|
||||||
signed: true,
|
|
||||||
});
|
});
|
||||||
res.cookie('refresh_token', authTokens.refresh_token, {
|
res.cookie('refresh_token', authTokens.refresh_token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
maxAge: refreshTokenValidity,
|
maxAge: refreshTokenValidity,
|
||||||
signed: true,
|
|
||||||
});
|
});
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
res.status(HttpStatus.OK).redirect(process.env.REDIRECT_URL);
|
res.status(HttpStatus.OK).redirect(process.env.REDIRECT_URL);
|
||||||
} else res.status(HttpStatus.OK).send();
|
} else res.status(HttpStatus.OK).send();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the cookie header from incoming websocket connects and returns a auth token pair
|
||||||
|
* @param rawCookies cookies from the websocket connection
|
||||||
|
* @returns AuthTokens for JWT strategy to use
|
||||||
|
*/
|
||||||
|
export const subscriptionContextCookieParser = (rawCookies: string) => {
|
||||||
|
const access_token = rawCookies.split(';')[0].split('=')[1];
|
||||||
|
const refresh_token = rawCookies.split(';')[1].split('=')[1];
|
||||||
|
return <AuthTokens>{
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromExtractors([
|
jwtFromRequest: ExtractJwt.fromExtractors([
|
||||||
(request: Request) => {
|
(request: Request) => {
|
||||||
const ATCookie = request.signedCookies['access_token'];
|
const ATCookie = request.cookies['access_token'];
|
||||||
if (!ATCookie) {
|
if (!ATCookie) {
|
||||||
throw new ForbiddenException(COOKIES_NOT_FOUND);
|
throw new ForbiddenException(COOKIES_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { GqlExecutionContext } from '@nestjs/graphql';
|
|||||||
export const GqlUser = createParamDecorator(
|
export const GqlUser = createParamDecorator(
|
||||||
(data: unknown, context: ExecutionContext) => {
|
(data: unknown, context: ExecutionContext) => {
|
||||||
const ctx = GqlExecutionContext.create(context);
|
const ctx = GqlExecutionContext.create(context);
|
||||||
return ctx.getContext().req.user;
|
const { req, headers } = ctx.getContext();
|
||||||
|
return headers ? headers.user : req.user;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { AuthGuard } from '@nestjs/passport';
|
|||||||
export class GqlAuthGuard extends AuthGuard('jwt') {
|
export class GqlAuthGuard extends AuthGuard('jwt') {
|
||||||
getRequest(context: ExecutionContext) {
|
getRequest(context: ExecutionContext) {
|
||||||
const ctx = GqlExecutionContext.create(context);
|
const ctx = GqlExecutionContext.create(context);
|
||||||
return ctx.getContext().req;
|
const { req, headers } = ctx.getContext();
|
||||||
|
return headers ? headers : req;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async function bootstrap() {
|
|||||||
app.enableVersioning({
|
app.enableVersioning({
|
||||||
type: VersioningType.URI,
|
type: VersioningType.URI,
|
||||||
});
|
});
|
||||||
app.use(cookieParser(process.env.SIGNED_COOKIE_SECRET));
|
app.use(cookieParser());
|
||||||
await app.listen(process.env.PORT || 3170);
|
await app.listen(process.env.PORT || 3170);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user