mirror of
https://github.com/wassname/talk.git
synced 2026-06-30 14:43:30 +08:00
feat: added google support
This commit is contained in:
Generated
+8
@@ -18727,6 +18727,14 @@
|
||||
"passport-oauth2": "1.x.x"
|
||||
}
|
||||
},
|
||||
"passport-google-oauth2": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz",
|
||||
"integrity": "sha1-39cBasdEn+J8/rJSrpdK/CMleg0=",
|
||||
"requires": {
|
||||
"passport-oauth2": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"passport-local": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"nunjucks": "^3.1.3",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-google-oauth2": "^0.1.6",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-oauth2": "^1.4.0",
|
||||
"passport-strategy": "^1.0.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ import passport, { Authenticator } from "passport";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import FacebookStrategy from "talk-server/app/middleware/passport/strategies/facebook";
|
||||
import GoogleStrategy from "talk-server/app/middleware/passport/strategies/google";
|
||||
import { JWTStrategy } from "talk-server/app/middleware/passport/strategies/jwt";
|
||||
import { createLocalStrategy } from "talk-server/app/middleware/passport/strategies/local";
|
||||
import OIDCStrategy from "talk-server/app/middleware/passport/strategies/oidc";
|
||||
@@ -54,6 +55,9 @@ export function createPassport(
|
||||
// Use the FacebookStrategy.
|
||||
auth.use(new FacebookStrategy(options));
|
||||
|
||||
// Use the GoogleStrategy.
|
||||
auth.use(new GoogleStrategy(options));
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ export default class FacebookStrategy extends OAuth2Strategy<
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe update user details?
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Db } from "mongodb";
|
||||
import { Profile, Strategy } from "passport-google-oauth2";
|
||||
|
||||
import { Config } from "talk-common/config";
|
||||
import OAuth2Strategy from "talk-server/app/middleware/passport/strategies/oauth2";
|
||||
import { reconstructTenantURL } from "talk-server/app/url";
|
||||
import {
|
||||
GQLAuthIntegrations,
|
||||
GQLGoogleAuthIntegration,
|
||||
GQLUSER_ROLE,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
import {
|
||||
GoogleProfile,
|
||||
retrieveUserWithProfile,
|
||||
} from "talk-server/models/user";
|
||||
import TenantCache from "talk-server/services/tenant/cache";
|
||||
import { upsert } from "talk-server/services/users";
|
||||
|
||||
export interface GoogleStrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
}
|
||||
|
||||
export interface GoogleStrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
}
|
||||
|
||||
export default class GoogleStrategy extends OAuth2Strategy<
|
||||
GQLGoogleAuthIntegration,
|
||||
Strategy
|
||||
> {
|
||||
public name = "google";
|
||||
|
||||
constructor(options: GoogleStrategyOptions) {
|
||||
super({
|
||||
...options,
|
||||
scope: ["profile"],
|
||||
});
|
||||
}
|
||||
|
||||
protected getIntegration = (integrations: GQLAuthIntegrations) =>
|
||||
integrations.google;
|
||||
|
||||
protected async findOrCreateUser(
|
||||
tenant: Tenant,
|
||||
integration: Required<GQLGoogleAuthIntegration>,
|
||||
{ id, photos, emails, displayName }: Profile
|
||||
) {
|
||||
// Create the user profile that will be used to lookup the User.
|
||||
const profile: GoogleProfile = {
|
||||
type: "google",
|
||||
id,
|
||||
};
|
||||
|
||||
let user = await retrieveUserWithProfile(this.mongo, tenant.id, profile);
|
||||
if (!user) {
|
||||
if (!integration.allowRegistration) {
|
||||
// Registration is disabled, so we can't create the user user here.
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: implement rules.
|
||||
|
||||
// Try to get the avatar.
|
||||
let avatar: string | undefined;
|
||||
if (photos && photos.length > 0) {
|
||||
avatar = photos[0].value;
|
||||
}
|
||||
|
||||
// Try to get the email address.
|
||||
let email: string | undefined;
|
||||
let emailVerified: boolean | undefined;
|
||||
if (emails && emails.length > 0) {
|
||||
email = emails[0].value;
|
||||
emailVerified = false;
|
||||
}
|
||||
|
||||
user = await upsert(this.mongo, tenant, {
|
||||
username: null,
|
||||
displayName,
|
||||
role: GQLUSER_ROLE.COMMENTER,
|
||||
email,
|
||||
email_verified: emailVerified,
|
||||
avatar,
|
||||
profiles: [profile],
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe update user details?
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
protected createStrategy(
|
||||
tenant: Tenant,
|
||||
integration: Required<GQLGoogleAuthIntegration>
|
||||
) {
|
||||
return new Strategy(
|
||||
{
|
||||
clientID: integration.clientID,
|
||||
clientSecret: integration.clientSecret,
|
||||
callbackURL: reconstructTenantURL(
|
||||
this.config,
|
||||
tenant,
|
||||
"/api/tenant/auth/google/callback"
|
||||
),
|
||||
passReqToCallback: true,
|
||||
},
|
||||
this.verifyCallback
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export interface OAuth2StrategyOptions {
|
||||
config: Config;
|
||||
mongo: Db;
|
||||
tenantCache: TenantCache;
|
||||
scope?: string[];
|
||||
}
|
||||
|
||||
export default abstract class OAuth2Strategy<
|
||||
@@ -30,13 +31,15 @@ export default abstract class OAuth2Strategy<
|
||||
protected config: Config;
|
||||
protected mongo: Db;
|
||||
protected cache: TenantCacheAdapter<U>;
|
||||
private scope?: string[];
|
||||
|
||||
constructor({ config, mongo, tenantCache }: OAuth2StrategyOptions) {
|
||||
constructor({ config, mongo, tenantCache, scope }: OAuth2StrategyOptions) {
|
||||
super();
|
||||
|
||||
this.config = config;
|
||||
this.mongo = mongo;
|
||||
this.cache = new TenantCacheAdapter(tenantCache);
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
protected abstract getIntegration(integrations: GQLAuthIntegrations): T;
|
||||
@@ -114,7 +117,10 @@ export default abstract class OAuth2Strategy<
|
||||
strategy.redirect = this.redirect.bind(this);
|
||||
strategy.success = this.success.bind(this);
|
||||
|
||||
strategy.authenticate(req, { session: false });
|
||||
strategy.authenticate(req, {
|
||||
session: false,
|
||||
scope: this.scope,
|
||||
});
|
||||
} catch (err) {
|
||||
return this.error(err);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,30 @@ import {
|
||||
import { wrapAuthn } from "talk-server/app/middleware/passport";
|
||||
import { RouterOptions } from "talk-server/app/router/types";
|
||||
|
||||
function wrapPath(
|
||||
app: AppOptions,
|
||||
options: RouterOptions,
|
||||
router: express.Router,
|
||||
strategy: string,
|
||||
path: string = `/${strategy}`
|
||||
) {
|
||||
const handler = wrapAuthn(options.passport, app.signingConfig, strategy);
|
||||
|
||||
router.get(path, handler);
|
||||
router.get(path + "/callback", handler);
|
||||
}
|
||||
|
||||
export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
|
||||
const router = express.Router();
|
||||
|
||||
// Mount the passport routes.
|
||||
// Mount the logout handler.
|
||||
router.delete(
|
||||
"/",
|
||||
options.passport.authenticate("jwt", { session: false }),
|
||||
logoutHandler({ redis: app.redis })
|
||||
);
|
||||
|
||||
// Mount the Local Authentication handlers.
|
||||
router.post(
|
||||
"/local",
|
||||
express.json(),
|
||||
@@ -29,24 +43,10 @@ export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
|
||||
signupHandler({ db: app.mongo, signingConfig: app.signingConfig })
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/facebook",
|
||||
wrapAuthn(options.passport, app.signingConfig, "facebook")
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/facebook/callback",
|
||||
wrapAuthn(options.passport, app.signingConfig, "facebook")
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/oidc/:oidcID",
|
||||
wrapAuthn(options.passport, app.signingConfig, "oidc")
|
||||
);
|
||||
router.get(
|
||||
"/oidc/:oidcID/callback",
|
||||
wrapAuthn(options.passport, app.signingConfig, "oidc")
|
||||
);
|
||||
// Mount the external auth integrations with middleware/handle wrappers.
|
||||
wrapPath(app, options, router, "facebook");
|
||||
wrapPath(app, options, router, "google");
|
||||
wrapPath(app, options, router, "oidc", "/oidc/:oidc");
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,17 @@ export interface FacebookProfile {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type Profile = LocalProfile | OIDCProfile | SSOProfile | FacebookProfile;
|
||||
export interface GoogleProfile {
|
||||
type: "google";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type Profile =
|
||||
| LocalProfile
|
||||
| OIDCProfile
|
||||
| SSOProfile
|
||||
| FacebookProfile
|
||||
| GoogleProfile;
|
||||
|
||||
export interface Token {
|
||||
readonly id: string;
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
declare module "passport-google-oauth2" {
|
||||
import express from "express";
|
||||
import passport from "passport";
|
||||
|
||||
export interface Profile extends passport.Profile {
|
||||
id: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export interface AuthenticateOptions extends passport.AuthenticateOptions {
|
||||
authType?: string;
|
||||
}
|
||||
|
||||
export interface StrategyOptionWithRequest {
|
||||
clientID: string;
|
||||
clientSecret: string;
|
||||
callbackURL: string;
|
||||
passReqToCallback: true;
|
||||
}
|
||||
|
||||
export type VerifyFunctionWithRequest = (
|
||||
req: express.Request,
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Profile,
|
||||
done: (error: any, user?: any, info?: any) => void
|
||||
) => void;
|
||||
|
||||
export class Strategy extends passport.Strategy {
|
||||
constructor(
|
||||
options: StrategyOptionWithRequest,
|
||||
verify: VerifyFunctionWithRequest
|
||||
);
|
||||
|
||||
public name: string;
|
||||
public authenticate(req: express.Request, options?: object): void;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user