Desktop support (#4484)
* Remove home link on desktop app * Spellcheck, installation toasts, background styling, … * Add email,slack, auth support * More desktop style tweaks * Move redirect to client * cleanup * Record desktop usage * docs * fix: Selection state in search input when double clicking header
This commit is contained in:
@@ -5,17 +5,17 @@ import type { Context } from "koa";
|
||||
import Router from "koa-router";
|
||||
import { Profile } from "passport";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner, {
|
||||
AccountProvisionerResult,
|
||||
} from "@server/commands/accountProvisioner";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import { MicrosoftGraphError } from "@server/errors";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
import { User } from "@server/models";
|
||||
import { AuthenticationResult } from "@server/types";
|
||||
import {
|
||||
StateStore,
|
||||
request,
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
@@ -49,7 +49,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
done: (
|
||||
err: Error | null,
|
||||
user: User | null,
|
||||
result?: AccountProvisionerResult
|
||||
result?: AuthenticationResult
|
||||
) => void
|
||||
) {
|
||||
try {
|
||||
@@ -94,6 +94,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
}
|
||||
|
||||
const team = await getTeamFromContext(ctx);
|
||||
const client = getClientFromContext(ctx);
|
||||
|
||||
const domain = email.split("@")[1];
|
||||
const subdomain = slugifyDomain(domain);
|
||||
@@ -124,7 +125,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
scopes,
|
||||
},
|
||||
});
|
||||
return done(null, result.user, result);
|
||||
return done(null, result.user, { ...result, client });
|
||||
} catch (err) {
|
||||
return done(err, null);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Router from "koa-router";
|
||||
import { find } from "lodash";
|
||||
import { Client } from "@shared/types";
|
||||
import { parseDomain } from "@shared/utils/domains";
|
||||
import { RateLimiterStrategy } from "@server/RateLimiter";
|
||||
import InviteAcceptedEmail from "@server/emails/templates/InviteAcceptedEmail";
|
||||
@@ -26,7 +27,7 @@ router.post(
|
||||
errorHandling(),
|
||||
rateLimiter(RateLimiterStrategy.TenPerHour),
|
||||
async (ctx) => {
|
||||
const { email } = ctx.request.body;
|
||||
const { email, client } = ctx.request.body;
|
||||
assertEmail(email, "email is required");
|
||||
|
||||
const domain = parseDomain(ctx.request.hostname);
|
||||
@@ -81,6 +82,7 @@ router.post(
|
||||
to: user.email,
|
||||
token: user.getEmailSigninToken(),
|
||||
teamUrl: team.url,
|
||||
client: client === Client.Desktop ? Client.Desktop : Client.Web,
|
||||
});
|
||||
user.lastSigninEmailSentAt = new Date();
|
||||
await user.save();
|
||||
@@ -93,7 +95,7 @@ router.post(
|
||||
);
|
||||
|
||||
router.get("email.callback", async (ctx) => {
|
||||
const { token } = ctx.request.query;
|
||||
const { token, client } = ctx.request.query;
|
||||
assertPresent(token, "token is required");
|
||||
|
||||
let user!: User;
|
||||
@@ -131,7 +133,13 @@ router.get("email.callback", async (ctx) => {
|
||||
}
|
||||
|
||||
// set cookies on response and redirect to team subdomain
|
||||
await signIn(ctx, user, user.team, "email", false, false);
|
||||
await signIn(ctx, "email", {
|
||||
user,
|
||||
team: user.team,
|
||||
isNewTeam: false,
|
||||
isNewUser: false,
|
||||
client: client === Client.Desktop ? Client.Desktop : Client.Web,
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -5,9 +5,7 @@ import { capitalize } from "lodash";
|
||||
import { Profile } from "passport";
|
||||
import { Strategy as GoogleStrategy } from "passport-google-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner, {
|
||||
AccountProvisionerResult,
|
||||
} from "@server/commands/accountProvisioner";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
GmailAccountCreationError,
|
||||
@@ -15,7 +13,12 @@ import {
|
||||
} from "@server/errors";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
import { User } from "@server/models";
|
||||
import { StateStore, getTeamFromContext } from "@server/utils/passport";
|
||||
import { AuthenticationResult } from "@server/types";
|
||||
import {
|
||||
StateStore,
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
const GOOGLE = "google";
|
||||
@@ -58,13 +61,14 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
done: (
|
||||
err: Error | null,
|
||||
user: User | null,
|
||||
result?: AccountProvisionerResult
|
||||
result?: AuthenticationResult
|
||||
) => void
|
||||
) {
|
||||
try {
|
||||
// "domain" is the Google Workspaces domain
|
||||
const domain = profile._json.hd;
|
||||
const team = await getTeamFromContext(ctx);
|
||||
const client = getClientFromContext(ctx);
|
||||
|
||||
// No profile domain means personal gmail account
|
||||
// No team implies the request came from the apex domain
|
||||
@@ -122,7 +126,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
},
|
||||
});
|
||||
|
||||
return done(null, result.user, result);
|
||||
return done(null, result.user, { ...result, client });
|
||||
} catch (err) {
|
||||
return done(err, null);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import Router from "koa-router";
|
||||
import { get } from "lodash";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner, {
|
||||
AccountProvisionerResult,
|
||||
} from "@server/commands/accountProvisioner";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
OIDCMalformedUserInfoError,
|
||||
@@ -14,10 +12,12 @@ import {
|
||||
} from "@server/errors";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
import { User } from "@server/models";
|
||||
import { AuthenticationResult } from "@server/types";
|
||||
import {
|
||||
StateStore,
|
||||
request,
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
@@ -73,7 +73,7 @@ if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
|
||||
done: (
|
||||
err: Error | null,
|
||||
user: User | null,
|
||||
result?: AccountProvisionerResult
|
||||
result?: AuthenticationResult
|
||||
) => void
|
||||
) {
|
||||
try {
|
||||
@@ -83,6 +83,7 @@ if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
|
||||
);
|
||||
}
|
||||
const team = await getTeamFromContext(ctx);
|
||||
const client = getClientFromContext(ctx);
|
||||
|
||||
const parts = profile.email.toLowerCase().split("@");
|
||||
const domain = parts.length && parts[1];
|
||||
@@ -123,7 +124,7 @@ if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
|
||||
scopes,
|
||||
},
|
||||
});
|
||||
return done(null, result.user, result);
|
||||
return done(null, result.user, { ...result, client });
|
||||
} catch (err) {
|
||||
return done(err, null);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ import type { Context } from "koa";
|
||||
import Router from "koa-router";
|
||||
import { Profile } from "passport";
|
||||
import { Strategy as SlackStrategy } from "passport-slack-oauth2";
|
||||
import accountProvisioner, {
|
||||
AccountProvisionerResult,
|
||||
} from "@server/commands/accountProvisioner";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
@@ -16,7 +14,12 @@ import {
|
||||
Team,
|
||||
User,
|
||||
} from "@server/models";
|
||||
import { getTeamFromContext, StateStore } from "@server/utils/passport";
|
||||
import { AuthenticationResult } from "@server/types";
|
||||
import {
|
||||
getClientFromContext,
|
||||
getTeamFromContext,
|
||||
StateStore,
|
||||
} from "@server/utils/passport";
|
||||
import * as Slack from "@server/utils/slack";
|
||||
import { assertPresent, assertUuid } from "@server/validation";
|
||||
|
||||
@@ -80,11 +83,13 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
|
||||
done: (
|
||||
err: Error | null,
|
||||
user: User | null,
|
||||
result?: AccountProvisionerResult
|
||||
result?: AuthenticationResult
|
||||
) => void
|
||||
) {
|
||||
try {
|
||||
const team = await getTeamFromContext(ctx);
|
||||
const client = getClientFromContext(ctx);
|
||||
|
||||
const result = await accountProvisioner({
|
||||
ip: ctx.ip,
|
||||
team: {
|
||||
@@ -110,7 +115,7 @@ if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
|
||||
scopes,
|
||||
},
|
||||
});
|
||||
return done(null, result.user, result);
|
||||
return done(null, result.user, { ...result, client });
|
||||
} catch (err) {
|
||||
return done(err, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user