redirect uri follows the user
This commit is contained in:
parent
21a1ceeb2d
commit
abb0e24d99
4
package-lock.json
generated
4
package-lock.json
generated
@ -2109,7 +2109,7 @@
|
||||
},
|
||||
"node_modules/@icynet/oauth2-provider": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#2877dac9374ac4509615dd0d4df5246b82cea3bd",
|
||||
"resolved": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.17.3",
|
||||
@ -13956,7 +13956,7 @@
|
||||
"dev": true
|
||||
},
|
||||
"@icynet/oauth2-provider": {
|
||||
"version": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#2877dac9374ac4509615dd0d4df5246b82cea3bd",
|
||||
"version": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a",
|
||||
"from": "@icynet/oauth2-provider@git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git",
|
||||
"requires": {
|
||||
"express": "^4.17.3",
|
||||
|
@ -7,6 +7,11 @@ export class ValidateCSRFMiddleware implements NestMiddleware {
|
||||
constructor(private readonly tokenService: TokenService) {}
|
||||
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
// Never try to validate these
|
||||
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Multipart is handeled elsewhere
|
||||
if (req.header('content-type')?.startsWith('multipart/form-data')) {
|
||||
return next();
|
||||
|
@ -35,8 +35,15 @@ export class LoginController {
|
||||
|
||||
@Get()
|
||||
@Render('login/login')
|
||||
public loginView(@Req() req: Request): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req);
|
||||
public loginView(
|
||||
@Req() req: Request,
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req, {
|
||||
query: redirectTo
|
||||
? new URLSearchParams({ redirectTo }).toString()
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ -44,7 +51,7 @@ export class LoginController {
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
@Body() body: { username: string; password: string },
|
||||
@Query() query: { redirectTo?: string },
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
) {
|
||||
const { username, password } = body;
|
||||
const user = await this.userService.getByUsername(username);
|
||||
@ -69,22 +76,21 @@ export class LoginController {
|
||||
const challenge = { type: 'verify', user: user.uuid };
|
||||
req.session.challenge = await this.token.encryptChallenge(challenge);
|
||||
res.redirect(
|
||||
'/login/verify' +
|
||||
(query.redirectTo ? '?redirectTo=' + query.redirectTo : ''),
|
||||
'/login/verify' + (redirectTo ? '?redirectTo=' + redirectTo : ''),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.user = user.uuid;
|
||||
res.redirect(query.redirectTo ? decodeURIComponent(query.redirectTo) : '/');
|
||||
res.redirect(redirectTo ? decodeURIComponent(redirectTo) : '/');
|
||||
}
|
||||
|
||||
@Get('verify')
|
||||
public verifyUserTokenView(
|
||||
@Session() session: SessionData,
|
||||
@Query() query: { redirectTo?: string },
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
) {
|
||||
if (!session.challenge) {
|
||||
req.flash('message', {
|
||||
@ -92,9 +98,7 @@ export class LoginController {
|
||||
text: 'An unexpected error occured, please log in again.',
|
||||
});
|
||||
|
||||
res.redirect(
|
||||
'/login' + (query.redirectTo ? '?redirectTo=' + query.redirectTo : ''),
|
||||
);
|
||||
res.redirect('/login' + (redirectTo ? '?redirectTo=' + redirectTo : ''));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -107,10 +111,10 @@ export class LoginController {
|
||||
@Post('verify')
|
||||
public async verifyUserToken(
|
||||
@Session() session: SessionData,
|
||||
@Query() query: { redirectTo?: string },
|
||||
@Body() body: { totp: string },
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
) {
|
||||
let user: User;
|
||||
|
||||
@ -134,9 +138,7 @@ export class LoginController {
|
||||
text: 'An unexpected error occured, please log in again.',
|
||||
});
|
||||
|
||||
res.redirect(
|
||||
'/login' + (query.redirectTo ? '?redirectTo=' + query.redirectTo : ''),
|
||||
);
|
||||
res.redirect('/login' + (redirectTo ? '?redirectTo=' + redirectTo : ''));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -157,7 +159,7 @@ export class LoginController {
|
||||
|
||||
session.challenge = null;
|
||||
session.user = user.uuid;
|
||||
res.redirect(query.redirectTo ? decodeURIComponent(query.redirectTo) : '/');
|
||||
res.redirect(redirectTo ? decodeURIComponent(redirectTo) : '/');
|
||||
}
|
||||
|
||||
@Get('activate')
|
||||
@ -165,9 +167,14 @@ export class LoginController {
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
@Query() query: { token: string },
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
) {
|
||||
const loginPath = `/login${
|
||||
redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''
|
||||
}`;
|
||||
let user: User;
|
||||
let token: UserToken;
|
||||
|
||||
try {
|
||||
if (!query || !query.token) {
|
||||
throw new Error();
|
||||
@ -189,7 +196,7 @@ export class LoginController {
|
||||
text: 'Invalid or expired activation link.',
|
||||
});
|
||||
|
||||
res.redirect('/login');
|
||||
res.redirect(loginPath);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -202,7 +209,7 @@ export class LoginController {
|
||||
text: 'Account has been activated successfully. You may now log in.',
|
||||
});
|
||||
|
||||
res.redirect('/login');
|
||||
res.redirect(loginPath);
|
||||
}
|
||||
|
||||
@Get('password')
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { AuthMiddleware } from 'src/middleware/auth.middleware';
|
||||
import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware';
|
||||
import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module';
|
||||
import { OAuth2TokenModule } from 'src/modules/objects/oauth2-token/oauth2-token.module';
|
||||
import { UploadModule } from 'src/modules/objects/upload/upload.module';
|
||||
@ -19,6 +20,8 @@ export class OAuth2Module implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(this._service.oauth.express()).forRoutes('oauth2/*');
|
||||
consumer.apply(this._service.oauth.bearer).forRoutes('oauth2/user');
|
||||
consumer.apply(AuthMiddleware).forRoutes('oauth2/authorize');
|
||||
consumer
|
||||
.apply(AuthMiddleware, ValidateCSRFMiddleware)
|
||||
.forRoutes('oauth2/authorize');
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class OAuth2Service {
|
||||
|
||||
public oauth = new OAuth2Provider(
|
||||
this._oauthAdapter,
|
||||
async (req, res, client, scope, user, redirectUri) => {
|
||||
async (req, res, client, scope) => {
|
||||
const fullClient = await this.clientService.getById(client.id as string);
|
||||
const allowedScopes = [...ALWAYS_AVAILABLE];
|
||||
const disallowedScopes = [...ALWAYS_UNAVAILABLE];
|
||||
@ -44,7 +44,7 @@ export class OAuth2Service {
|
||||
});
|
||||
|
||||
res.render('authorize', {
|
||||
csrf: req.session.csrf,
|
||||
csrf: req.csrfToken(),
|
||||
user: req.user,
|
||||
client: fullClient,
|
||||
allowedScopes,
|
||||
|
@ -35,7 +35,7 @@ export class RegisterController {
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
@Body() body: RegisterDto,
|
||||
@Query() query: { redirectTo?: string },
|
||||
@Query('redirectTo') redirectTo?: string,
|
||||
) {
|
||||
const { username, display_name, email, password, password_repeat } = body;
|
||||
|
||||
@ -77,16 +77,14 @@ export class RegisterController {
|
||||
throw new Error('The passwords do not match!');
|
||||
}
|
||||
|
||||
await this.userService.userRegistration(body);
|
||||
await this.userService.userRegistration(body, redirectTo);
|
||||
|
||||
req.flash('message', {
|
||||
error: false,
|
||||
text: `An activation email has been sent to ${email}!`,
|
||||
});
|
||||
|
||||
res.redirect(
|
||||
'/login' + (query.redirectTo ? '?redirectTo=' + query.redirectTo : ''),
|
||||
);
|
||||
res.redirect('/login' + (redirectTo ? '?redirectTo=' + redirectTo : ''));
|
||||
} catch (e: any) {
|
||||
req.flash('message', { error: true, text: e.message });
|
||||
req.flash('form', { ...body, password_repeat: undefined });
|
||||
|
@ -95,19 +95,27 @@ export class UserService {
|
||||
return bcrypt.hash(password, salt);
|
||||
}
|
||||
|
||||
public async sendActivationEmail(user: User): Promise<void> {
|
||||
public async sendActivationEmail(
|
||||
user: User,
|
||||
redirectTo?: string,
|
||||
): Promise<void> {
|
||||
const activationToken = await this.userToken.create(
|
||||
user,
|
||||
UserTokenType.ACTIVATION,
|
||||
new Date(Date.now() + 3600 * 1000),
|
||||
);
|
||||
|
||||
const params = new URLSearchParams({ token: activationToken.token });
|
||||
if (redirectTo) {
|
||||
params.append('redirectTo', redirectTo);
|
||||
}
|
||||
|
||||
try {
|
||||
const content = RegistrationEmail(
|
||||
user.username,
|
||||
`${this.config.get<string>('app.base_url')}/login/activate?token=${
|
||||
activationToken.token
|
||||
}`,
|
||||
`${this.config.get<string>(
|
||||
'app.base_url',
|
||||
)}/login/activate?${params.toString()}`,
|
||||
);
|
||||
await this.email.sendEmailTemplate(
|
||||
user.email,
|
||||
@ -154,12 +162,15 @@ export class UserService {
|
||||
await this.sendPasswordEmail(user);
|
||||
}
|
||||
|
||||
public async userRegistration(newUserInfo: {
|
||||
username: string;
|
||||
display_name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<User> {
|
||||
public async userRegistration(
|
||||
newUserInfo: {
|
||||
username: string;
|
||||
display_name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
},
|
||||
redirectTo?: string,
|
||||
): Promise<User> {
|
||||
if (!!(await this.getByEmail(newUserInfo.email))) {
|
||||
throw new Error('Email is already in use!');
|
||||
}
|
||||
@ -179,7 +190,7 @@ export class UserService {
|
||||
await this.userRepository.insert(user);
|
||||
|
||||
try {
|
||||
await this.sendActivationEmail(user);
|
||||
await this.sendActivationEmail(user, redirectTo);
|
||||
} catch (e) {
|
||||
await this.userRepository.remove(user);
|
||||
throw new Error(
|
||||
|
@ -10,7 +10,10 @@ const ALGORITHM = 'aes-256-cbc';
|
||||
|
||||
@Injectable()
|
||||
export class TokenService {
|
||||
public csrf = new CSRF();
|
||||
public csrf = new CSRF({
|
||||
saltLength: 16,
|
||||
secretLength: 32,
|
||||
});
|
||||
|
||||
constructor(private config: ConfigurationService) {}
|
||||
|
||||
|
@ -25,7 +25,7 @@ block body
|
||||
input.form-control#password(type="password", name="password", placeholder="Password")
|
||||
button.btn.btn-primary(type="submit") Log in
|
||||
div.btn-group.align-self-end
|
||||
a.btn.btn-link(type="button" href="/register") Create a new account
|
||||
a.btn.btn-link(type="button" href="/register" + (query ? ('?' + query) : '')) Create a new account
|
||||
|•
|
||||
a.btn.btn-link(type="button" href="/login/password") Forgot password?
|
||||
div.center-box-addon
|
||||
|
Reference in New Issue
Block a user