fix(auth): redirect URL after signing in by SSO sucessfully (#3387)

* fix: fix T-2900

* fix: saml
This commit is contained in:
YANG QIA 2024-01-14 22:26:13 +08:00 committed by GitHub
parent 88dd3dd569
commit dc71a77d8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 36 additions and 22 deletions

View File

@ -6,17 +6,17 @@ import { getSubAppName } from '@nocobase/sdk';
import { Authenticator } from '@nocobase/plugin-auth/client'; import { Authenticator } from '@nocobase/plugin-auth/client';
export const SigninPage = (props: { authenticator: Authenticator }) => { export const SigninPage = (props: { authenticator: Authenticator }) => {
const location = useLocation();
const authenticator = props.authenticator; const authenticator = props.authenticator;
const location = useLocation();
const params = new URLSearchParams(location.search);
const redirect = params.get('redirect');
const app = getSubAppName() || 'main'; const app = getSubAppName() || 'main';
const login = async () => { const login = async () => {
window.location.replace(`/api/cas:login?authenticator=${authenticator.name}&__appName=${app}`); window.location.replace(`/api/cas:login?authenticator=${authenticator.name}&__appName=${app}&redirect=${redirect}`);
}; };
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search);
const name = params.get('authenticator'); const name = params.get('authenticator');
const error = params.get('error'); const error = params.get('error');
if (name !== authenticator.name) { if (name !== authenticator.name) {

View File

@ -9,7 +9,7 @@ export class SamlPlugin extends Plugin {
const auth = this.app.pm.get(AuthPlugin); const auth = this.app.pm.get(AuthPlugin);
auth.registerType(authType, { auth.registerType(authType, {
components: { components: {
SignInForm: SigninPage, SignInButton: SigninPage,
AdminSettingsForm: Options, AdminSettingsForm: Options,
}, },
}); });

View File

@ -2,10 +2,12 @@ import { Context, Next } from '@nocobase/actions';
import { CASAuth } from '../auth'; import { CASAuth } from '../auth';
export const login = async (ctx: Context, next: Next) => { export const login = async (ctx: Context, next: Next) => {
const { authenticator } = ctx.action.params; const { authenticator, redirect = '' } = ctx.action.params;
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth; const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth;
const { casUrl, serviceUrl } = auth.getOptions(); const { casUrl, serviceUrl } = auth.getOptions();
const service = encodeURIComponent(`${serviceUrl}?authenticator=${authenticator}&__appName=${ctx.app.name}`); const service = encodeURIComponent(
`${serviceUrl}?authenticator=${authenticator}&__appName=${ctx.app.name}&redirect=${redirect}`,
);
ctx.redirect(`${casUrl}/login?service=${service}`); ctx.redirect(`${casUrl}/login?service=${service}`);
next(); next();
}; };

View File

@ -3,7 +3,7 @@ import { AppSupervisor } from '@nocobase/server';
import { CASAuth } from '../auth'; import { CASAuth } from '../auth';
export const service = async (ctx: Context, next: Next) => { export const service = async (ctx: Context, next: Next) => {
const { authenticator, __appName: appName } = ctx.action.params; const { authenticator, __appName: appName, redirect = '/admin' } = ctx.action.params;
let prefix = ''; let prefix = '';
if (appName && appName !== 'main') { if (appName && appName !== 'main') {
@ -16,9 +16,9 @@ export const service = async (ctx: Context, next: Next) => {
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth; const auth = (await ctx.app.authManager.get(authenticator, ctx)) as CASAuth;
try { try {
const { token } = await auth.signIn(); const { token } = await auth.signIn();
ctx.redirect(`${prefix}/admin?authenticator=${authenticator}&token=${token}`); ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
} catch (error) { } catch (error) {
ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}`); ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}&redirect=${redirect}`);
} }
return next(); return next();
}; };

View File

@ -15,6 +15,8 @@ export const OIDCButton = ({ authenticator }: { authenticator: Authenticator })
const { t } = useOidcTranslation(); const { t } = useOidcTranslation();
const api = useAPIClient(); const api = useAPIClient();
const location = useLocation(); const location = useLocation();
const params = new URLSearchParams(location.search);
const redirect = params.get('redirect');
const login = async () => { const login = async () => {
const response = await api.request({ const response = await api.request({
@ -23,6 +25,9 @@ export const OIDCButton = ({ authenticator }: { authenticator: Authenticator })
headers: { headers: {
'X-Authenticator': authenticator.name, 'X-Authenticator': authenticator.name,
}, },
data: {
redirect,
},
}); });
const authUrl = response?.data?.data; const authUrl = response?.data?.data;
@ -30,7 +35,6 @@ export const OIDCButton = ({ authenticator }: { authenticator: Authenticator })
}; };
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search);
const name = params.get('authenticator'); const name = params.get('authenticator');
const error = params.get('error'); const error = params.get('error');
if (name !== authenticator.name) { if (name !== authenticator.name) {

View File

@ -4,6 +4,7 @@ import { nanoid } from 'nanoid';
import { cookieName } from '../../constants'; import { cookieName } from '../../constants';
export const getAuthUrl = async (ctx: Context, next: Next) => { export const getAuthUrl = async (ctx: Context, next: Next) => {
const { redirect = '' } = ctx.action.params.values || {};
const app = ctx.app.name; const app = ctx.app.name;
const auth = ctx.auth as OIDCAuth; const auth = ctx.auth as OIDCAuth;
const client = await auth.createOIDCClient(); const client = await auth.createOIDCClient();
@ -16,8 +17,8 @@ export const getAuthUrl = async (ctx: Context, next: Next) => {
ctx.body = client.authorizationUrl({ ctx.body = client.authorizationUrl({
response_type: 'code', response_type: 'code',
scope: scope || 'openid email profile', scope: scope || 'openid email profile',
redirect_uri: auth.getRedirectUri(), redirect_uri: `${auth.getRedirectUri()}`,
state: encodeURIComponent(`token=${token}&name=${ctx.headers['x-authenticator']}&app=${app}`), state: encodeURIComponent(`token=${token}&name=${ctx.headers['x-authenticator']}&app=${app}&redirect=${redirect}`),
}); });
return next(); return next();

View File

@ -9,6 +9,7 @@ export const redirect = async (ctx: Context, next: Next) => {
const search = new URLSearchParams(decodeURIComponent(state)); const search = new URLSearchParams(decodeURIComponent(state));
const authenticator = search.get('name'); const authenticator = search.get('name');
const appName = search.get('app'); const appName = search.get('app');
const redirect = search.get('redirect') || '/admin';
let prefix = ''; let prefix = '';
if (appName && appName !== 'main') { if (appName && appName !== 'main') {
const appSupervisor = AppSupervisor.getInstance(); const appSupervisor = AppSupervisor.getInstance();
@ -19,10 +20,10 @@ export const redirect = async (ctx: Context, next: Next) => {
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as OIDCAuth; const auth = (await ctx.app.authManager.get(authenticator, ctx)) as OIDCAuth;
try { try {
const { token } = await auth.signIn(); const { token } = await auth.signIn();
ctx.redirect(`${prefix}/admin?authenticator=${authenticator}&token=${token}`); ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
} catch (error) { } catch (error) {
ctx.logger.error('OIDC auth error', { error }); ctx.logger.error('OIDC auth error', { error });
ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}`); ctx.redirect(`${prefix}/signin?redirect=${redirect}&authenticator=${authenticator}&error=${error.message}`);
} }
await next(); await next();
}; };

View File

@ -10,6 +10,8 @@ export const SAMLButton = ({ authenticator }: { authenticator: Authenticator })
const { t } = useSamlTranslation(); const { t } = useSamlTranslation();
const api = useAPIClient(); const api = useAPIClient();
const location = useLocation(); const location = useLocation();
const params = new URLSearchParams(location.search);
const redirect = params.get('redirect');
const login = async () => { const login = async () => {
const response = await api.request({ const response = await api.request({
@ -18,6 +20,9 @@ export const SAMLButton = ({ authenticator }: { authenticator: Authenticator })
headers: { headers: {
'X-Authenticator': authenticator.name, 'X-Authenticator': authenticator.name,
}, },
data: {
redirect,
},
}); });
const authUrl = response?.data?.data; const authUrl = response?.data?.data;
@ -25,7 +30,6 @@ export const SAMLButton = ({ authenticator }: { authenticator: Authenticator })
}; };
useEffect(() => { useEffect(() => {
const params = new URLSearchParams(location.search);
const name = params.get('authenticator'); const name = params.get('authenticator');
const error = params.get('error'); const error = params.get('error');
if (name !== authenticator.name) { if (name !== authenticator.name) {

View File

@ -3,11 +3,12 @@ import { SAML } from '@node-saml/node-saml';
import { SAMLAuth } from '../saml-auth'; import { SAMLAuth } from '../saml-auth';
export const getAuthUrl = async (ctx: Context, next: Next) => { export const getAuthUrl = async (ctx: Context, next: Next) => {
const { redirect = '' } = ctx.action.params.values || {};
const auth = ctx.auth as SAMLAuth; const auth = ctx.auth as SAMLAuth;
const options = auth.getOptions(); const options = auth.getOptions();
const saml = new SAML(options); const saml = new SAML(options);
ctx.body = await saml.getAuthorizeUrlAsync('', '', {}); ctx.body = await saml.getAuthorizeUrlAsync(redirect, '', {});
return next(); return next();
}; };

View File

@ -4,6 +4,7 @@ import { AppSupervisor } from '@nocobase/server';
export const redirect = async (ctx: Context, next: Next) => { export const redirect = async (ctx: Context, next: Next) => {
const { authenticator, __appName: appName } = ctx.action.params || {}; const { authenticator, __appName: appName } = ctx.action.params || {};
const { RelayState: redirect = '/admin' } = ctx.action.params.values || {};
let prefix = ''; let prefix = '';
if (appName && appName !== 'main') { if (appName && appName !== 'main') {
const appSupervisor = AppSupervisor.getInstance(); const appSupervisor = AppSupervisor.getInstance();
@ -14,9 +15,9 @@ export const redirect = async (ctx: Context, next: Next) => {
const auth = (await ctx.app.authManager.get(authenticator, ctx)) as SAMLAuth; const auth = (await ctx.app.authManager.get(authenticator, ctx)) as SAMLAuth;
try { try {
const { token } = await auth.signIn(); const { token } = await auth.signIn();
ctx.redirect(`${prefix}/admin?authenticator=${authenticator}&token=${token}`); ctx.redirect(`${prefix}${redirect}?authenticator=${authenticator}&token=${token}`);
} catch (error) { } catch (error) {
ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}`); ctx.redirect(`${prefix}/signin?authenticator=${authenticator}&error=${error.message}&redirect=${redirect}`);
} }
await next(); await next();
}; };

View File

@ -18,19 +18,19 @@ export class SAMLAuth extends BaseAuth {
}); });
} }
getOptions() { getOptions(): SamlConfig {
const ctx = this.ctx; const ctx = this.ctx;
const { ssoUrl, certificate, idpIssuer, http }: SAMLOptions = this.options?.saml || {}; const { ssoUrl, certificate, idpIssuer, http }: SAMLOptions = this.options?.saml || {};
const name = this.authenticator.get('name'); const name = this.authenticator.get('name');
const protocol = http ? 'http' : 'https'; const protocol = http ? 'http' : 'https';
return { return {
callbackUrl: `${protocol}://${ctx.host}/api/saml:redirect?authenticator=${name}&app=${ctx.app.name}`, callbackUrl: `${protocol}://${ctx.host}/api/saml:redirect?authenticator=${name}&__appName=${ctx.app.name}`,
entryPoint: ssoUrl, entryPoint: ssoUrl,
issuer: name, issuer: name,
cert: certificate, cert: certificate,
idpIssuer, idpIssuer,
wantAssertionsSigned: false, wantAssertionsSigned: false,
} as SamlConfig; };
} }
async validate() { async validate() {