downloads by user id (#2500)

This commit is contained in:
Tessa Thornton
2019-08-23 16:05:54 -04:00
committed by Kim Gardner
parent ffecd5e981
commit 0e37a474fa
7 changed files with 158 additions and 3 deletions
@@ -27,6 +27,8 @@ export type DownloadOptions = Pick<
"mongo" | "redis" | "signingConfig" | "config"
>;
export type AdminDownloadOptions = Pick<AppOptions, "mongo" | "signingConfig">;
async function sendExport(
mongo: Db,
tenant: Tenant,
@@ -216,6 +218,46 @@ export const downloadHandler = ({
};
};
export const adminDownloadHandler = ({
mongo,
signingConfig,
}: AdminDownloadOptions): RequestHandler => {
return async (req: Request, res, next) => {
// Tenant is guaranteed at this point.
const coral = req.coral!;
const tenant = coral.tenant!;
const { token } = req.query;
const { sub: userID } = decodeJWT(token);
if (!userID) {
return res.sendStatus(400);
}
try {
const {
token: { iat },
user,
} = await verifyDownloadTokenString(
mongo,
tenant,
signingConfig,
token,
coral.now
);
// Only load comments since this download token was issued.
const latestContentDate = new Date(iat * 1000);
// Send the export down the response.
await sendExport(mongo, tenant, user, latestContentDate, res);
return;
} catch (err) {
return next(err);
}
};
};
export type DownloadCheckOptions = Pick<
AppOptions,
"mongo" | "redis" | "signingConfig" | "config"
@@ -3,6 +3,7 @@ import express from "express";
import { AppOptions } from "coral-server/app";
import {
adminDownloadHandler,
confirmCheckHandler,
confirmHandler,
confirmRequestHandler,
@@ -34,6 +35,7 @@ export function createNewAccountRouter(
router.put("/invite", jsonMiddleware, inviteHandler(app));
router.get("/downloadcheck", downloadCheckHandler(app));
router.get("/download", adminDownloadHandler(app));
router.post(
"/download",
bodyParser.urlencoded({
@@ -11,6 +11,7 @@ import {
removeIgnore,
removeSuspension,
requestCommentsDownload,
requestUserCommentsDownload,
setEmail,
setPassword,
setUsername,
@@ -35,6 +36,7 @@ import {
GQLRemoveUserIgnoreInput,
GQLRemoveUserSuspensionInput,
GQLRequestCommentsDownloadInput,
GQLRequestUserCommentsDownloadInput,
GQLSetEmailInput,
GQLSetPasswordInput,
GQLSetUsernameInput,
@@ -187,6 +189,17 @@ export const Users = (ctx: TenantContext) => ({
ignore(ctx.mongo, ctx.tenant, ctx.user!, input.userID, ctx.now),
removeIgnore: async (input: GQLRemoveUserIgnoreInput) =>
removeIgnore(ctx.mongo, ctx.tenant, ctx.user!, input.userID),
requestUserCommentsDownload: async (
input: GQLRequestUserCommentsDownloadInput
) =>
requestUserCommentsDownload(
ctx.mongo,
ctx.tenant,
ctx.config,
ctx.signingConfig!,
input.userID,
ctx.now
),
requestCommentsDownload: async (input: GQLRequestCommentsDownloadInput) =>
requestCommentsDownload(
ctx.mongo,
@@ -189,4 +189,8 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
user: await ctx.mutators.Users.requestCommentsDownload(input),
clientMutationId: input.clientMutationId,
}),
requestUserCommentsDownload: async (sourc, { input }, ctx) => ({
archiveURL: await ctx.mutators.Users.requestUserCommentsDownload(input),
clientMutationId: input.clientMutationId,
}),
};
@@ -4579,6 +4579,33 @@ type RequestCommentsDownloadPayload {
clientMutationId: String!
}
#########################
# requestUserCommentsDownload
#########################
input RequestUserCommentsDownloadInput {
"""
userID specifies user to download comments for
"""
userID: String!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
type RequestUserCommentsDownloadPayload {
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
archiveURL is the archive url
"""
archiveURL: String!
}
##################
## Mutation
##################
@@ -4862,6 +4889,13 @@ type Mutation {
requestCommentsDownload(
input: RequestCommentsDownloadInput!
): RequestCommentsDownloadPayload! @auth(permit: [SUSPENDED, BANNED])
"""
requestUserCommentsDownload allows a user to request to download their comments.
"""
requestUserCommentsDownload(
input: RequestUserCommentsDownloadInput!
): RequestUserCommentsDownloadPayload! @auth(roles: [ADMIN])
}
##################
@@ -25,7 +25,7 @@ const DownloadTokenSchema = StandardClaimsSchema.keys({
aud: Joi.string().only("download"),
});
export async function generateDownloadLink(
export async function generateDownloadToken(
userID: string,
tenant: Tenant,
config: Config,
@@ -46,7 +46,23 @@ export async function generateDownloadLink(
aud: "download",
};
const token = await signString(signingConfig, downloadToken);
return await signString(signingConfig, downloadToken);
}
export async function generateDownloadLink(
userID: string,
tenant: Tenant,
config: Config,
signingConfig: JWTSigningConfig,
now: Date
) {
const token = await generateDownloadToken(
userID,
tenant,
config,
signingConfig,
now
);
return constructTenantURL(
config,
@@ -55,6 +71,28 @@ export async function generateDownloadLink(
);
}
export async function generateAdminDownloadLink(
userID: string,
tenant: Tenant,
config: Config,
signingConfig: JWTSigningConfig,
now: Date
) {
const token = await generateDownloadToken(
userID,
tenant,
config,
signingConfig,
now
);
return constructTenantURL(
config,
tenant,
`/api/account/download?token=${token}`
);
}
export function validateDownloadToken(
token: DownloadToken | object
): Error | null {
+23 -1
View File
@@ -65,7 +65,10 @@ import { sendConfirmationEmail } from "coral-server/services/users/auth";
import { JWTSigningConfig, signPATString } from "coral-server/services/jwt";
import { generateDownloadLink } from "./download/download";
import {
generateAdminDownloadLink,
generateDownloadLink,
} from "./download/download";
import { validateEmail, validatePassword, validateUsername } from "./helpers";
export type InsertUser = InsertUserInput;
@@ -917,3 +920,22 @@ export async function requestCommentsDownload(
return user;
}
export async function requestUserCommentsDownload(
mongo: Db,
tenant: Tenant,
config: Config,
signingConfig: JWTSigningConfig,
userID: string,
now: Date
) {
const downloadUrl = await generateAdminDownloadLink(
userID,
tenant,
config,
signingConfig,
now
);
return downloadUrl;
}