Skip to content
32 changes: 27 additions & 5 deletions apps/backend/src/authn/authn.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import {LocalAuthGuard} from '../guards/local-auth.guard';
import {LoggingInterceptor} from '../interceptors/logging.interceptor';
import {User} from '../users/user.model';
import {AuthnService} from './authn.service';

import 'express-session';
declare module 'express-session' {
interface SessionData {
redirectLogin?: string;
}
}
@UseInterceptors(LoggingInterceptor)
@Controller('authn')
export class AuthnController {
Expand Down Expand Up @@ -85,7 +90,15 @@ export class AuthnController {
this.logger.debug('in the github login callback func');
this.logger.debug(JSON.stringify(req.session, null, 2));
const session = await this.authnService.login(req.user as User);
await this.setSessionCookies(req, session);
const redirectTarget =
typeof req.session.redirectLogin === 'string' &&
req.session.redirectLogin.startsWith('/')
? req.session.redirectLogin
: undefined;

delete req.session.redirectLogin;

await this.setSessionCookies(req, session, redirectTarget);
}

@Get('gitlab')
Expand Down Expand Up @@ -169,22 +182,31 @@ export class AuthnController {
this.logger.debug('in the oidc login callback func');
this.logger.debug(JSON.stringify(req.session, null, 2));
const session = await this.authnService.login(req.user as User);
await this.setSessionCookies(req, session);
const redirectTarget =
typeof req.session.redirectLogin === 'string' &&
req.session.redirectLogin.startsWith('/')
? req.session.redirectLogin
: undefined;

delete req.session.redirectLogin;

await this.setSessionCookies(req, session, redirectTarget);
}

async setSessionCookies(
req: Request,
session: {
userID: string;
accessToken: string;
}
},
redirectTarget?: string
): Promise<void> {
req.res?.cookie('userID', session.userID, {
secure: this.configService.isInProductionMode()
});
req.res?.cookie('accessToken', session.accessToken, {
secure: this.configService.isInProductionMode()
});
req.res?.redirect('/');
req.res?.redirect(redirectTarget || '/');
}
}
38 changes: 33 additions & 5 deletions apps/backend/src/authn/github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {Strategy} from 'passport-github';
import {ConfigService} from '../config/config.service';
import {User} from '../users/user.model';
import {AuthnService} from './authn.service';

import {Request} from 'express';
import 'express-session';
declare module 'express-session' {
interface SessionData {
redirectLogin?: string;
}
}
interface GithubProfile {
name: string | null;
login: string;
Expand Down Expand Up @@ -43,10 +49,32 @@ export class GithubStrategy extends PassportStrategy(Strategy, 'github') {
});
}

async validate(
req: Record<string, unknown>,
accessToken: string
): Promise<User> {
authenticate(req: Request, options: Record<string, unknown> = {}) {
const redirect =
typeof req.query?.redirect === 'string' &&
req.query.redirect.startsWith('/')
? req.query.redirect
: undefined;
if (redirect) {
super.authenticate(req, {
...options,
state: encodeURIComponent(redirect)
});
return;
}
super.authenticate(req);
}

async validate(req: Request, accessToken: string): Promise<User> {
const redirectLogin =
typeof req.query?.state === 'string'
? decodeURIComponent(req.query.state as string)
: undefined;

if (redirectLogin?.startsWith('/')) {
req.session.redirectLogin = redirectLogin;
}

// Get user's linked emails from Github
const githubEmails = await axios
.get<GithubEmail[]>(
Expand Down
33 changes: 33 additions & 0 deletions apps/backend/src/authn/oidc.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import winston from 'winston';
import {ConfigService} from '../config/config.service';
import {GroupsService} from '../groups/groups.service';
import {AuthnService} from './authn.service';
import {Request} from 'express';
import 'express-session';
declare module 'express-session' {
interface SessionData {
redirectLogin?: string;
}
}

interface OIDCProfile {
id: string;
Expand All @@ -24,6 +31,22 @@ interface OIDCProfile {

@Injectable()
export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
authenticate(req: Request, options: Record<string, unknown> = {}) {
const redirect =
typeof req.query?.redirect === 'string' &&
req.query.redirect.startsWith('/')
? req.query.redirect
: undefined;
if (redirect) {
super.authenticate(req, {
...options,
state: encodeURIComponent(redirect)
});
return;
}
super.authenticate(req);
}

private readonly line = '_______________________________________________\n';
public loggingTimeFormat = 'MMM-DD-YYYY HH:mm:ss Z';
public logger = winston.createLogger({
Expand Down Expand Up @@ -73,6 +96,7 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
},
// using the 9-arity function so that we can access the underlying JSON response and extract the 'email_verified' attribute
async (
req: Request,
_issuer: string,
uiProfile: OIDCProfile,
_idProfile: object,
Expand All @@ -84,6 +108,15 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') {
//eslint-disable-next-line @typescript-eslint/no-explicit-any
done: any
) => {
const redirectLogin =
typeof req.query?.state === 'string'
? decodeURIComponent(req.query.state as string)
: undefined;

if (redirectLogin?.startsWith('/')) {
req.session.redirectLogin = redirectLogin;
}

this.logger.debug('in oidc strategy file');
this.logger.debug(JSON.stringify(uiProfile, null, 2));
const userData = uiProfile._json;
Expand Down
14 changes: 13 additions & 1 deletion apps/frontend/src/components/global/login/LDAPLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,19 @@ export default class LDAPLogin extends Vue {
password: this.password
};
ServerModule.LoginLDAP(creds).then(() => {
this.$router.push('/');
if (ServerModule.token) {
const redirectQuery = this.$route.query.redirect;
const redirectTarget = Array.isArray(redirectQuery)
? redirectQuery[0]
: redirectQuery;

const destination =
typeof redirectTarget === 'string' && redirectTarget.startsWith('/')
? redirectTarget
: '/';

this.$router.push(destination);
}
SnackbarModule.notify('You have successfully signed in.');
});
}
Expand Down
28 changes: 26 additions & 2 deletions apps/frontend/src/components/global/login/LocalLogin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,19 @@ export default class LocalLogin extends Vue {
};
ServerModule.Login(creds)
.then(() => {
this.$router.push('/');
if (ServerModule.token) {
const redirectQuery = this.$route.query.redirect;
const redirectTarget = Array.isArray(redirectQuery)
? redirectQuery[0]
: redirectQuery;

const destination =
typeof redirectTarget === 'string' && redirectTarget.startsWith('/')
? redirectTarget
: '/';

this.$router.push(destination);
}
SnackbarModule.notify('You have successfully signed in.');
})
.finally(() => {
Expand All @@ -219,7 +231,19 @@ export default class LocalLogin extends Vue {
}

oauthLogin(site: string) {
window.location.href = `/authn/${site}`;
const redirectQuery = this.$route.query.redirect;
const redirectTarget = Array.isArray(redirectQuery)
? redirectQuery[0]
: redirectQuery;

const destination =
typeof redirectTarget === 'string' && redirectTarget.startsWith('/')
? redirectTarget
: '/';

const url = `/authn/${site}?redirect=${encodeURIComponent(destination)}`;

window.location.href = url;
}

get oidcName() {
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ router.beforeEach((to, _, next) => {
AppInfoModule.CheckForUpdates();
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (ServerModule.serverMode && !ServerModule.token) {
next('/login');
next({path: '/login', query: {redirect: to.fullPath}});
return;
}
}
Expand Down
12 changes: 11 additions & 1 deletion apps/frontend/src/views/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,17 @@ export default class Login extends Vue {

checkLoggedIn() {
if (ServerModule.token) {
this.$router.push('/');
const redirectQuery = this.$route.query.redirect;
const redirectTarget = Array.isArray(redirectQuery)
? redirectQuery[0]
: redirectQuery;

const destination =
typeof redirectTarget === 'string' && redirectTarget.startsWith('/')
? redirectTarget
: '/';

this.$router.push(destination);
}
}

Expand Down
Loading