double auth maintenant fonctionne correctement. Il y a un PoC avec svelte
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Controller, Get, Res, UseGuards, Req, Post, UnauthorizedException, Body, Options } from '@nestjs/common';
|
import { Controller, Get, Res, UseGuards, Req, Post, UnauthorizedException, Body, Options, Next } from '@nestjs/common';
|
||||||
import { AuthenticateGuard, FortyTwoAuthGuard } from './guards/42guards';
|
import { AuthenticateGuard, FortyTwoAuthGuard, TwoFactorGuard } from './guards/42guards';
|
||||||
import { AuthenticationService } from './authentication.service';
|
import { AuthenticationService } from './authentication.service';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { TwoFaDto } from './dto/2fa.dto';
|
import { TwoFaDto } from './dto/2fa.dto';
|
||||||
@@ -31,19 +31,26 @@ export class AuthenticationController {
|
|||||||
@Get('redirect')
|
@Get('redirect')
|
||||||
@UseGuards(FortyTwoAuthGuard)
|
@UseGuards(FortyTwoAuthGuard)
|
||||||
async redirect(@Res() response : Response, @Req() request) {
|
async redirect(@Res() response : Response, @Req() request) {
|
||||||
console.log('ON EST DANS REDIRECT AUTH CONTROLLER' + request.user);
|
console.log('ON EST DANS REDIRECT AUTH CONTROLLER');
|
||||||
console.log('On redirige');
|
console.log('On redirige');
|
||||||
return response.status(200).redirect('http://transcendance:8080');
|
if (request.user.isEnabledTwoFactorAuth === false)
|
||||||
|
return response.status(200).redirect('http://transcendance:8080');
|
||||||
|
return response.status(200).redirect('http://transcendance:8080/#/2fa');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v2/auth/logout
|
* GET /api/v2/auth/logout
|
||||||
* Route pour déconnecter l'utilisateur
|
* Route pour déconnecter l'utilisateur
|
||||||
*/
|
*/
|
||||||
@Get('logout')
|
@Post('logout')
|
||||||
logout(@Req() request) {
|
@UseGuards(AuthenticateGuard)
|
||||||
console.log('ON EST DANS LOGOUT AUTH CONTROLLER')
|
logout(@Req() request, @Res() response, @Next() next) {
|
||||||
request.session.destroy();
|
this.userService.setIsTwoFactorAuthenticatedWhenLogout(request.user.id);
|
||||||
|
request.logout(function(err) {
|
||||||
|
if (err) { return next(err); }
|
||||||
|
response.redirect('/');
|
||||||
|
});
|
||||||
|
request.session.cookie.maxAge = 0;
|
||||||
return {msg : 'You are now logged out'};
|
return {msg : 'You are now logged out'};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +64,7 @@ export class AuthenticationController {
|
|||||||
|
|
||||||
@Post('2fa/turn-on')
|
@Post('2fa/turn-on')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
async verify(@Req() request, @Body() {twoFaCode} : TwoFaDto){
|
async verify(@Req() request, @Body() {twoFaCode} : TwoFaDto, @Res() response){
|
||||||
console.log('ON EST DANS VERIFY POUR 2FA AUTH CONTROLLER')
|
console.log('ON EST DANS VERIFY POUR 2FA AUTH CONTROLLER')
|
||||||
const isCodeIsValid = await this.authService.verify2FaCode(request.user, twoFaCode);
|
const isCodeIsValid = await this.authService.verify2FaCode(request.user, twoFaCode);
|
||||||
if (isCodeIsValid === false)
|
if (isCodeIsValid === false)
|
||||||
@@ -65,5 +72,7 @@ export class AuthenticationController {
|
|||||||
throw new UnauthorizedException('Wrong Code.');
|
throw new UnauthorizedException('Wrong Code.');
|
||||||
}
|
}
|
||||||
await this.userService.enableTwoFactorAuth(request.user.id);
|
await this.userService.enableTwoFactorAuth(request.user.id);
|
||||||
|
console.log('ON REDIRIGE');
|
||||||
|
return response.status(200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,18 @@ export class FortyTwoAuthGuard extends AuthGuard('42') {
|
|||||||
export class AuthenticateGuard implements CanActivate {
|
export class AuthenticateGuard implements CanActivate {
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const request = context.switchToHttp().getRequest();
|
const request = context.switchToHttp().getRequest();
|
||||||
console.log("Is User authenticated : " + request.isAuthenticated());
|
console.log("AuthenticateGuard : Is User authenticated : " + request.isAuthenticated());
|
||||||
return request.isAuthenticated();
|
return request.isAuthenticated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TwoFactorGuard implements CanActivate {
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
console.log("TwoFactorGuard : Is User authenticated : " + request.isAuthenticated() + " and has 2FA enabled : " + request.user.isEnabledTwoFactorAuth + " and has 2FA verified : " + request.user.isTwoFactorAuthenticated);
|
||||||
|
return request.isAuthenticated() && (request.user.isEnabledTwoFactorAuth === false
|
||||||
|
|| request.user.isTwoFactorAuthenticated === true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Patch, Post, Query, Req, UseGuards } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Patch, Post, Query, Req, UseGuards } from '@nestjs/common';
|
||||||
import { AuthenticateGuard } from 'src/auth/42/guards/42guards';
|
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
||||||
import { User } from 'src/users/entities/user.entity';
|
import { User } from 'src/users/entities/user.entity';
|
||||||
import { CreateFriendshipDto } from './dto/create-friendship.dto';
|
import { CreateFriendshipDto } from './dto/create-friendship.dto';
|
||||||
import { FriendshipService } from './friendship.service';
|
import { FriendshipService } from './friendship.service';
|
||||||
@@ -11,6 +11,7 @@ export class FriendshipController {
|
|||||||
// GET http://127.0.0.1:3000/api/v2/network/myfriends
|
// GET http://127.0.0.1:3000/api/v2/network/myfriends
|
||||||
@Get('myfriends')
|
@Get('myfriends')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
findEmpty(@Req() req) {
|
findEmpty(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllFriends(user.id);
|
return this.friendshipService.findAllFriends(user.id);
|
||||||
@@ -19,6 +20,7 @@ export class FriendshipController {
|
|||||||
// GET http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId
|
// GET http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId
|
||||||
@Get('myfriends/:relationshipId')
|
@Get('myfriends/:relationshipId')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
findOneFriend(@Param('relationshipId') relationshipId: string, @Req() req) {
|
findOneFriend(@Param('relationshipId') relationshipId: string, @Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findOneFriend(relationshipId, user.id);
|
return this.friendshipService.findOneFriend(relationshipId, user.id);
|
||||||
@@ -28,6 +30,7 @@ export class FriendshipController {
|
|||||||
@Post('myfriends')
|
@Post('myfriends')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
|
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
console.log(`User id: ${user.id}\n Friend id: ${createFriendshipDto.requesterId}`);
|
console.log(`User id: ${user.id}\n Friend id: ${createFriendshipDto.requesterId}`);
|
||||||
@@ -39,6 +42,7 @@ export class FriendshipController {
|
|||||||
// PATCH http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId?status=A
|
// PATCH http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId?status=A
|
||||||
@Patch('myfriends/:relationshipId')
|
@Patch('myfriends/:relationshipId')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
update(@Param('relationshipId') relationshipId: string, @Query('status') status : string, @Req() req)
|
update(@Param('relationshipId') relationshipId: string, @Query('status') status : string, @Req() req)
|
||||||
{
|
{
|
||||||
const user : User = req.user;
|
const user : User = req.user;
|
||||||
@@ -48,6 +52,7 @@ export class FriendshipController {
|
|||||||
// DELETE http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId
|
// DELETE http://127.0.0.1:3000/api/v2/network/myfriends/relationshipId
|
||||||
@Delete('myfriends/:relationshipId')
|
@Delete('myfriends/:relationshipId')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
remove(@Param('relationshipId') relationshipId: string) {
|
remove(@Param('relationshipId') relationshipId: string) {
|
||||||
return this.friendshipService.removeFriendship(relationshipId);
|
return this.friendshipService.removeFriendship(relationshipId);
|
||||||
}
|
}
|
||||||
@@ -56,6 +61,7 @@ export class FriendshipController {
|
|||||||
// GET http://127.0.0.1:3000/api/v2/network/blocked
|
// GET http://127.0.0.1:3000/api/v2/network/blocked
|
||||||
@Get('blocked')
|
@Get('blocked')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
findAllBlocked(@Req() req) {
|
findAllBlocked(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllBlockedFriends(user.id);
|
return this.friendshipService.findAllBlockedFriends(user.id);
|
||||||
@@ -64,6 +70,7 @@ export class FriendshipController {
|
|||||||
// GET http://127.0.0.1:3000/api/v2/network/pending
|
// GET http://127.0.0.1:3000/api/v2/network/pending
|
||||||
@Get('pending')
|
@Get('pending')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
findAllPendantFriendshipRequested(@Req() req) {
|
findAllPendantFriendshipRequested(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllPendantRequestsForFriendship(user.id);
|
return this.friendshipService.findAllPendantRequestsForFriendship(user.id);
|
||||||
@@ -72,6 +79,7 @@ export class FriendshipController {
|
|||||||
// GET http://127.0.0.1:3000/api/v2/network/received
|
// GET http://127.0.0.1:3000/api/v2/network/received
|
||||||
@Get('received')
|
@Get('received')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
findAllPendantFriendshipReceived(@Req() req) {
|
findAllPendantFriendshipReceived(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllReceivedRequestsForFriendship(user.id);
|
return this.friendshipService.findAllReceivedRequestsForFriendship(user.id);
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export class User {
|
|||||||
@Column({ default: false, nullable: true })
|
@Column({ default: false, nullable: true })
|
||||||
isEnabledTwoFactorAuth: boolean;
|
isEnabledTwoFactorAuth: boolean;
|
||||||
|
|
||||||
|
@Column({ default:false, nullable: true })
|
||||||
|
isTwoFactorAuthenticated: boolean;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
secretTwoFactorAuth: string;
|
secretTwoFactorAuth: string;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
Body, Controller, Delete, Get, HttpCode,
|
Body, Controller, Delete, Get, HttpCode,
|
||||||
HttpStatus, Param, Patch, Post, Query, Req, UseGuards
|
HttpStatus, Param, Patch, Post, Query, Req, UseGuards
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { AuthenticateGuard } from 'src/auth/42/guards/42guards';
|
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
||||||
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||||
import { ValidationPipe } from 'src/common/validation/validation.pipe';
|
import { ValidationPipe } from 'src/common/validation/validation.pipe';
|
||||||
import { CreateUsersDto } from './dto/create-users.dto';
|
import { CreateUsersDto } from './dto/create-users.dto';
|
||||||
@@ -17,6 +17,7 @@ export class UsersController {
|
|||||||
// par exemple dans postamn ou insomnia http://localhost:3000/users?limit=10&offset=20
|
// par exemple dans postamn ou insomnia http://localhost:3000/users?limit=10&offset=20
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
@Get('all')
|
@Get('all')
|
||||||
findAll(@Query() paginationquery : PaginationQueryDto) {
|
findAll(@Query() paginationquery : PaginationQueryDto) {
|
||||||
//const { limit, offset } = query;
|
//const { limit, offset } = query;
|
||||||
@@ -33,6 +34,7 @@ export class UsersController {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
@Get()
|
@Get()
|
||||||
findOne(@Req() req) {
|
findOne(@Req() req) {
|
||||||
return this.usersService.findOne(req.user.id);
|
return this.usersService.findOne(req.user.id);
|
||||||
@@ -46,6 +48,7 @@ export class UsersController {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
@Patch()
|
@Patch()
|
||||||
update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto) {
|
update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto) {
|
||||||
console.log("DANS PATCH USERS");
|
console.log("DANS PATCH USERS");
|
||||||
@@ -53,6 +56,7 @@ export class UsersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
@Delete()
|
@Delete()
|
||||||
remove(@Req() req) {
|
remove(@Req() req) {
|
||||||
return this.usersService.remove(req.user.id);
|
return this.usersService.remove(req.user.id);
|
||||||
|
|||||||
@@ -82,7 +82,11 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async enableTwoFactorAuth(id: string) {
|
async enableTwoFactorAuth(id: string) {
|
||||||
return this.userRepository.update(id, {isEnabledTwoFactorAuth: true});
|
return this.userRepository.update(id, {isEnabledTwoFactorAuth: true, isTwoFactorAuthenticated: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setIsTwoFactorAuthenticatedWhenLogout(id: number) {
|
||||||
|
return this.userRepository.update(id, {isTwoFactorAuthenticated: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAuthenticatorSecret(id: number, secret: string) {
|
async setAuthenticatorSecret(id: number, secret: string) {
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
<script>
|
<script>
|
||||||
import Login from "./pages/auth/login.svelte";
|
import Login from "./pages/auth/login.svelte";
|
||||||
import Home from "./pages/home/home.svelte";
|
import Home from "./pages/home/home.svelte";
|
||||||
import Router, {link} from 'svelte-spa-router';
|
import Router, {link, push} from 'svelte-spa-router';
|
||||||
import Profil from "./pages/profil/profil.svelte";
|
import Profil from "./pages/profil/profil.svelte";
|
||||||
import UpdateProfil from "./pages/profil/updateProfil.svelte";
|
import UpdateProfil from "./pages/profil/updateProfil.svelte";
|
||||||
|
import DoubleFa from "./pages/auth/DoubleFa.svelte";
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
"/": Home,
|
"/": Home,
|
||||||
"/login": Login,
|
"/login": Login,
|
||||||
"/profil": Profil,
|
"/profil": Profil,
|
||||||
"/update-profil": UpdateProfil,
|
"/update-profil": UpdateProfil,
|
||||||
|
"/2fa": DoubleFa,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$: logout = async() => {
|
||||||
|
await fetch("http://transcendance:8080/api/v2/auth/logout",{
|
||||||
|
method : 'POST',
|
||||||
|
}).then(push('/login'));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="p-3 text-bg-dark">
|
<header class="p-3 text-bg-dark">
|
||||||
@@ -27,7 +34,11 @@
|
|||||||
<a href="/profil" use:link type="button" class="btn btn-primary">Profil</a>
|
<a href="/profil" use:link type="button" class="btn btn-primary">Profil</a>
|
||||||
</li> -->
|
</li> -->
|
||||||
</ul>
|
</ul>
|
||||||
|
<div>
|
||||||
|
<button on:click={logout} class="w-100 btn btn-lg btn-primary" type="submit">
|
||||||
|
Deconnexion
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<a href="/login" use:link type="button" class="btn btn-warning">Connexion</a>
|
<a href="/login" use:link type="button" class="btn btn-warning">Connexion</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script>
|
||||||
|
import { push } from "svelte-spa-router";
|
||||||
|
|
||||||
|
let qrCodeImg;
|
||||||
|
let qrCode = "";
|
||||||
|
let wrongCode = "";
|
||||||
|
let maxTry = 3;
|
||||||
|
const fetchQrCodeImg = (async() => {
|
||||||
|
await fetch("http://transcendance:8080/api/v2/auth/2fa/generate",
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
.then(response => {return response.blob()})
|
||||||
|
.then(blob => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
qrCodeImg = url;
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
|
||||||
|
$: submit = async() => {
|
||||||
|
const response = await fetch("http://transcendance:8080/api/v2/auth/2fa/turn-on",
|
||||||
|
{
|
||||||
|
method : 'POST',
|
||||||
|
headers : {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body : JSON.stringify({
|
||||||
|
"twoFaCode" : qrCode,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (response.status === 401)
|
||||||
|
{
|
||||||
|
qrCode = "";
|
||||||
|
wrongCode = `Wrong code, please try again. You have ${maxTry} before end session`;
|
||||||
|
maxTry--;
|
||||||
|
}
|
||||||
|
if (maxTry === 0)
|
||||||
|
{
|
||||||
|
await fetch("http://transcendance:8080/auth/logout",
|
||||||
|
{
|
||||||
|
method : 'POST',
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(push("/login"));
|
||||||
|
}
|
||||||
|
if (response.status === 200)
|
||||||
|
{
|
||||||
|
push("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main class="form-signin w-100 m-auto">
|
||||||
|
{#await fetchQrCodeImg}
|
||||||
|
<p>Please Wait...</p>
|
||||||
|
{:then data}
|
||||||
|
{#if wrongCode}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{wrongCode}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<img src={qrCodeImg} alt="A QRCodeImg you must scan with google authenticator" id="qrcodeImg" />
|
||||||
|
<form on:submit|preventDefault={submit}>
|
||||||
|
<label for="code" class="block text-sm text-gray-600">Code</label>
|
||||||
|
<input id="code" bind:value={qrCode} type="text" class="block px-1 py-2 mt-2 border-2 border-gray-100 text-gray-800" />
|
||||||
|
<button type="submit" class="p-2 bg-blue-500 text-white mt-4 px-6">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{:catch}
|
||||||
|
<p>Unable to get QrCodeImg</p>
|
||||||
|
{/await}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user