Browse Source

auth and chat back start

master
nicolas-arnaud 2 years ago
parent
commit
e733309787
  1. 3
      .env
  2. 14
      .env_sample
  3. 2
      .gitignore
  4. 2
      Makesudo
  5. 3
      README.md
  6. 15
      back/Dockerfile
  7. 28
      back/entrypoint.sh
  8. 1795
      back/volume/package-lock.json
  9. 23
      back/volume/package.json
  10. 28
      back/volume/src/api/api.controller.ts
  11. 10
      back/volume/src/api/api.module.ts
  12. 15
      back/volume/src/app.module.ts
  13. 25
      back/volume/src/auth/42-auth.guard.ts
  14. 9
      back/volume/src/auth/42.decorator.ts
  15. 42
      back/volume/src/auth/42.strategy.ts
  16. 21
      back/volume/src/auth/auth.controller.ts
  17. 14
      back/volume/src/auth/auth.module.ts
  18. 24
      back/volume/src/auth/session.serializer.ts
  19. 15
      back/volume/src/chat/chat.module.ts
  20. 33
      back/volume/src/chat/entities/channel.entity.ts
  21. 28
      back/volume/src/chat/entities/message.entity.ts
  22. 36
      back/volume/src/db/db.module.ts
  23. 38
      back/volume/src/main.ts
  24. 5
      back/volume/src/types.d.ts
  25. 39
      back/volume/src/users/user.dto.ts
  26. 19
      back/volume/src/users/user.entity.ts
  27. 30
      back/volume/src/users/users.controller.ts
  28. 13
      back/volume/src/users/users.module.ts
  29. 54
      back/volume/src/users/users.service.ts
  30. 3
      back/volume/tsconfig.json
  31. 1
      docker-compose.yml
  32. 1
      front/Dockerfile
  33. 6
      package-lock.json

3
.env

@ -1,3 +0,0 @@
POSTGRES_USER: postgres_usr
POSTGRES_PASSWORD: postgres_pw
POSTGRES_DB: transcendence

14
.env_sample

@ -0,0 +1,14 @@
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: postgres_usr
POSTGRES_PASSWORD: postgres_pw
POSTGRES_DB: transcendence
BACK_PORT=3001
JWT_SECRET=
JWT_EXPIRATION_TIME_SECONDS=900
FT_OAUTH_CLIENT_ID=
FT_OAUTH_CLIENT_SECRET=
FT_OAUTH_CALLBACK_URL=http://localhost/

2
.gitignore

@ -0,0 +1,2 @@
.env
*/volume/.env

2
Makesudo

@ -24,7 +24,7 @@ clean: stop
sudo docker system prune -f sudo docker system prune -f
fclean: stop fclean: stop
rm -rf */volume/node_modules rm */volume/node_modules
sudo docker system prune -af --volumes sudo docker system prune -af --volumes
re: fclean dev re: fclean dev

3
README.md

@ -11,6 +11,9 @@ If you not use rootless docker, either rename Makesudo as Makefile or call `make
- check: format and lint back and check front. - check: format and lint back and check front.
- debug: launch back with debug flags. - debug: launch back with debug flags.
### Setting:
rename .env_sample to .env and customize it to your needs and credentials.
## Dependencies: ## Dependencies:

15
back/Dockerfile

@ -3,15 +3,8 @@ FROM alpine:3.15
RUN apk update && apk upgrade && apk add npm \ RUN apk update && apk upgrade && apk add npm \
&& npm install -g @nestjs/cli && npm install -g @nestjs/cli
WORKDIR /var/www/html WORKDIR /var/www/html
ENTRYPOINT npm install; \
if [[ $NODE_ENV == "production" ]]; then \ COPY entrypoint.sh /tmp/entrypoint.sh
npm run build && npm run start:prod; \ ENTRYPOINT ["sh", "/tmp/entrypoint.sh"]
elif [[ $NODE_ENV == "debug" ]]; then \
npm run start:debug; \
elif [[ $NODE_ENV == "check" ]]; then \
npm run format && npm run lint; echo "=== FINISH ==="; \
elif [[ $NODE_ENV == "development" ]]; then \
npm run dev; \
else echo "NODE_ENV value isn't known."; \
fi;

28
back/entrypoint.sh

@ -0,0 +1,28 @@
npm install;
cat >.env <<EOF
POSTGRES_HOST= $POSTGRES_HOST
POSTGRES_PORT= $POSTGRES_PORT
POSTGRES_USER= $POSTGRES_USER
POSTGRES_PASSWORD= $POSTGRES_PASSWORD
POSTGRES_DB= $POSTGRES_DB
BACK_PORT=$BACK_PORT
JWT_SECRET=$JWT_SECRET
JWT_EXPIRATION_TIME_SECONDS=$JWT_EXPIRATION_TIME_SECONDS
FT_OAUTH_CLIENT_ID=$FT_OAUTH_CLIENT_ID
FT_OAUTH_CLIENT_SECRET=$FT_OAUTH_CLIENT_SECRET
FT_OAUTH_CALLBACK_URL=$FT_OAUTH_CALLBACK_URL
EOF
if [[ $NODE_ENV == "production" ]]; then
npm run build && npm run start:prod;
elif [[ $NODE_ENV == "debug" ]]; then
npm run start:debug;
elif [[ $NODE_ENV == "check" ]]; then
npm run format && npm run lint; echo "=== FINISH ===";
elif [[ $NODE_ENV == "development" ]]; then
npm run dev;
else echo "NODE_ENV value isn't known.";
fi;

1795
back/volume/package-lock.json

File diff suppressed because it is too large

23
back/volume/package.json

@ -21,25 +21,40 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@nestjs/axios": "^2.0.0",
"@nestjs/common": "^9.0.0", "@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"@nestjs/platform-ws": "^9.2.0", "@nestjs/platform-ws": "^9.2.0",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@nestjs/typeorm": "^9.0.1",
"@nestjs/websockets": "^9.2.0", "@nestjs/websockets": "^9.2.0",
"@types/cookie-parser": "^1.4.3",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.6",
"@types/passport": "^1.0.12",
"@types/node": "^16.0.0", "@types/node": "^16.0.0",
"@types/ws": "^8.5.3", "@types/ws": "^8.5.3",
"class-validator": "^0.14.0",
"class-transformer": "^0.5.1",
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"joi": "^17.8.3",
"passport": "^0.6.0",
"passport-42": "^1.2.6",
"passport-jwt": "^4.0.1",
"pg": "^8.9.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
"ws": "^8.11.0", "typeorm": "^0.3.12",
"class-transformer": "^0.5.1", "ws": "^8.11.0"
"class-validator": "^0.14.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8", "@types/jest": "28.1.8",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.53.0", "@typescript-eslint/eslint-plugin": "^5.53.0",

28
back/volume/src/api/api.controller.ts

@ -0,0 +1,28 @@
import { Controller, Get, Redirect, Req, UseGuards } from '@nestjs/common'
import { User } from 'src/auth/42.decorator'
import { AuthenticatedGuard } from 'src/auth/42-auth.guard'
import { Profile } from 'passport-42'
import { Request } from 'express'
@Controller()
export class ApiController {
@Get('profile')
@UseGuards(AuthenticatedGuard)
profile (@User() user: Profile) {
return { user }
}
@Get('logout')
@Redirect('/')
logOut (@Req() req: Request) {
req.logOut(function (err) {
if (err) return err
})
}
@Get('ranks')
getRanks (): number[] {
return [1, 2, 3]
}
}

10
back/volume/src/api/api.module.ts

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common'
import { HttpModule } from '@nestjs/axios'
import { ApiController } from './api.controller'
@Module({
imports: [HttpModule],
controllers: [ApiController],
})
export class ApiModule { }

15
back/volume/src/app.module.ts

@ -1,7 +1,20 @@
import { Module } from '@nestjs/common' import { Module } from '@nestjs/common'
import { ApiModule } from './api/api.module'
import { AuthModule } from './auth/auth.module'
import { ChatModule } from './chat/chat.module'
import { DbModule } from './db/db.module'
import { PongModule } from './pong/pong.module' import { PongModule } from './pong/pong.module'
import { UsersModule } from './users/users.module'
@Module({ @Module({
imports: [PongModule] imports: [
ApiModule,
AuthModule,
ChatModule,
DbModule,
PongModule,
UsersModule
],
}) })
export class AppModule { } export class AppModule { }

25
back/volume/src/auth/42-auth.guard.ts

@ -0,0 +1,25 @@
import {
type ExecutionContext,
Injectable,
type CanActivate
} from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { type Request } from 'express'
@Injectable()
export class FtOauthGuard extends AuthGuard('42') {
async canActivate (context: ExecutionContext): Promise<boolean> {
const activate: boolean = (await super.canActivate(context)) as boolean
const request: Request = context.switchToHttp().getRequest()
await super.logIn(request)
return activate
}
}
@Injectable()
export class AuthenticatedGuard implements CanActivate {
async canActivate (context: ExecutionContext): Promise<boolean> {
const req: Request = context.switchToHttp().getRequest()
return req.isAuthenticated()
}
}

9
back/volume/src/auth/42.decorator.ts

@ -0,0 +1,9 @@
import { createParamDecorator, type ExecutionContext } from '@nestjs/common'
import { type Profile } from 'passport-42'
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext): Profile => {
const request = ctx.switchToHttp().getRequest()
return request.user
}
)

42
back/volume/src/auth/42.strategy.ts

@ -0,0 +1,42 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, Profile, VerifyCallback } from 'passport-42'
import { UsersService } from 'src/users/users.service'
import { User } from 'src/users/user.entity'
@Injectable()
export class FtStrategy extends PassportStrategy(Strategy, '42') {
constructor (
private readonly configService: ConfigService,
private readonly usersService: UsersService
) {
super({
clientID: configService.get<string>('FT_OAUTH_CLIENT_ID'),
clientSecret: configService.get<string>('FT_OAUTH_CLIENT_SECRET'),
callbackURL: configService.get<string>('FT_OAUTH_CALLBACK_URL'),
passReqToCallback: true
})
}
async validate (
request: { session: { accessToken: string } },
accessToken: string,
refreshToken: string,
profile: Profile,
cb: VerifyCallback
): Promise<any> {
request.session.accessToken = accessToken
console.log('accessToken', accessToken, 'refreshToken', refreshToken)
const id_42 = profile.id as number
console.log(profile)
if ((await this.usersService.getOneUser42(id_42)) === null) {
const newUser = new User()
newUser.id_42 = profile.id as number
newUser.username = profile.displayName as string
newUser.avatar = profile._json.image.versions.small as string
this.usersService.create(newUser)
}
return cb(null, profile)
}
}

21
back/volume/src/auth/auth.controller.ts

@ -0,0 +1,21 @@
import { Controller, Get, Redirect, UseGuards, Res, Req } from '@nestjs/common'
import { FtOauthGuard } from './42-auth.guard'
import { Response, Request } from 'express'
@Controller('auth')
export class AuthController {
@Get('42')
@UseGuards(FtOauthGuard)
ftAuth () {}
@Get('42/return')
@UseGuards(FtOauthGuard)
@Redirect('http://localhost:5000/')
ftAuthCallback (
@Res({ passthrough: true }) response: Response,
@Req() request: Request
) {
console.log('cookie:', request.cookies['connect.sid'])
response.cookie('connect.sid', request.cookies['connect.sid'])
}
}

14
back/volume/src/auth/auth.module.ts

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common'
import { UsersModule } from 'src/users/users.module'
import { PassportModule } from '@nestjs/passport'
import { ConfigService } from '@nestjs/config'
import { AuthController } from './auth.controller'
import { FtStrategy } from './42.strategy'
import { SessionSerializer } from './session.serializer'
@Module({
imports: [UsersModule, PassportModule],
providers: [ConfigService, FtStrategy, SessionSerializer],
controllers: [AuthController]
})
export class AuthModule {}

24
back/volume/src/auth/session.serializer.ts

@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common'
import { PassportSerializer } from '@nestjs/passport'
import { type Profile } from 'passport-42'
@Injectable()
export class SessionSerializer extends PassportSerializer {
constructor () {
super()
}
serializeUser (
user: Profile,
done: (err: Error | null, user: Profile) => void
): any {
done(null, user)
}
deserializeUser (
payload: Profile,
done: (err: Error | null, user: Profile) => void
) {
done(null, payload)
}
}

15
back/volume/src/chat/chat.module.ts

@ -0,0 +1,15 @@
import { Module, forwardRef } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { Message } from './entities/message.entity'
import { Channel } from './entities/channel.entity'
import { UsersModule } from 'src/users/users.module'
@Module({
imports: [
TypeOrmModule.forFeature([Channel, Message]),
forwardRef(() => UsersModule)
]
// providers: [ChatService]
})
export class ChatModule {}

33
back/volume/src/chat/entities/channel.entity.ts

@ -0,0 +1,33 @@
import {
Entity,
PrimaryGeneratedColumn,
JoinTable,
OneToMany,
ManyToMany
} from 'typeorm'
import { Message } from './message.entity'
import { User } from 'src/users/user.entity'
@Entity('channel')
export class Channel {
@PrimaryGeneratedColumn()
id: number
@OneToMany(() => Message, (message) => message.channel)
message: Message[]
@ManyToMany(() => User)
@JoinTable({
name: 'users_id',
joinColumn: {
name: 'channel',
referencedColumnName: 'id'
},
inverseJoinColumn: {
name: 'user',
referencedColumnName: 'id'
}
})
user: User[]
}

28
back/volume/src/chat/entities/message.entity.ts

@ -0,0 +1,28 @@
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
JoinColumn,
ManyToOne
} from 'typeorm'
import { Channel } from './channel.entity'
import { User } from 'src/users/user.entity'
@Entity('message')
export class Message extends BaseEntity {
@PrimaryGeneratedColumn()
public id: number
@Column()
public content: string
@ManyToOne(() => User)
@JoinColumn({ name: 'author_id' })
public author: User
@ManyToOne(() => Channel)
@JoinColumn({ name: 'channel_id' })
public channel: Channel
}

36
back/volume/src/db/db.module.ts

@ -0,0 +1,36 @@
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { ConfigModule, ConfigService } from '@nestjs/config'
import * as Joi from 'joi'
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
POSTGRES_HOST: Joi.string().required(),
POSTGRES_PORT: Joi.number().required(),
POSTGRES_USER: Joi.string().required(),
POSTGRES_PASSWORD: Joi.string().required(),
POSTGRES_DB: Joi.string().required(),
BACK_PORT: Joi.number(),
})
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get<string>('POSTGRES_HOST'),
port: configService.get<number>('POSTGRES_PORT'),
username: configService.get<string>('POSTGRES_USER'),
password: configService.get<string>('POSTGRES_PASSWORD'),
database: configService.get<string>('POSTGRES_DB'),
jwt_secret: configService.get<string>('JWT_SECRET'),
entities: [__dirname + '/../**/*.entity.ts'],
synchronize: true
})
}),
]
})
export class DbModule { }

38
back/volume/src/main.ts

@ -1,10 +1,38 @@
import { NestFactory } from '@nestjs/core'
import { WsAdapter } from '@nestjs/platform-ws' import { WsAdapter } from '@nestjs/platform-ws'
import { Logger } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module' import { AppModule } from './app.module'
import * as session from 'express-session'
import * as passport from 'passport'
import { type NestExpressApplication } from '@nestjs/platform-express'
import * as cookieParser from 'cookie-parser'
async function bootstrap (): Promise<void> { async function bootstrap () {
const app = await NestFactory.create(AppModule) const logger = new Logger()
const app = await NestFactory.create<NestExpressApplication>(AppModule)
const port = process.env.BACK_PORT
const cors = {
origin: ['http://localhost:80', 'http://localhost', '*'],
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS',
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
allowedHeaders: ['Accept', 'Content-Type', 'Authorization']
}
app.use(
session({
resave: false,
saveUninitialized: false,
secret: process.env.JWT_SECRET
})
)
app.use(cookieParser())
app.use(passport.initialize())
app.use(passport.session())
app.enableCors(cors)
app.useWebSocketAdapter(new WsAdapter(app)) app.useWebSocketAdapter(new WsAdapter(app))
await app.listen(3001) await app.listen(port)
logger.log(`Application listening on port ${port}`)
} }
void bootstrap() bootstrap()

5
back/volume/src/types.d.ts

@ -0,0 +1,5 @@
declare module 'passport-42' {
export type Profile = any;
export type VerifyCallback = any;
export class Strategy {};
}

39
back/volume/src/users/user.dto.ts

@ -0,0 +1,39 @@
import {
IsString,
IsNotEmpty,
IsEmail,
Length,
IsPositive,
IsOptional
} from 'class-validator'
export class CreateUserDto {
@IsPositive()
@IsNotEmpty()
readonly id_42: number
@IsString()
@IsNotEmpty()
readonly username: string
@IsString()
@IsNotEmpty()
readonly avatar: string
}
export class UpdateUserDto {
@IsPositive()
@IsNotEmpty()
readonly id_42: number
@IsString()
@IsNotEmpty()
readonly username: string
@IsString()
@IsNotEmpty()
readonly avatar: string
@IsOptional()
readonly status: string
}

19
back/volume/src/users/user.entity.ts

@ -0,0 +1,19 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
id_42: number
@Column({ unique: true })
username: string
@Column()
avatar: string
@Column({ default: 'online' })
status: string
}

30
back/volume/src/users/users.controller.ts

@ -0,0 +1,30 @@
import {
Controller,
Get,
Post,
Body,
Param,
ParseIntPipe
} from '@nestjs/common'
import { type User } from './user.entity'
import { UsersService } from './users.service'
import { CreateUserDto, UpdateUserDto } from './user.dto'
@Controller('users')
export class UsersController {
constructor (private readonly usersService: UsersService) {}
@Get()
async getAllUsers (): Promise<User[]> {
return await this.usersService.getAllUsers()
}
@Post()
async create (@Body() payload: CreateUserDto) {
return await this.usersService.create(payload)
}
@Post(':id')
update (@Param('id', ParseIntPipe) id: number, @Body() user: UpdateUserDto) {
this.usersService.update(id, user)
}
}

13
back/volume/src/users/users.module.ts

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from './user.entity'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService]
})
export class UsersModule {}

54
back/volume/src/users/users.service.ts

@ -0,0 +1,54 @@
import {
ConflictException,
Injectable,
NotFoundException
} from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './user.entity'
import { type CreateUserDto, type UpdateUserDto } from './user.dto'
@Injectable()
export class UsersService {
constructor (
@InjectRepository(User) private readonly usersRepository: Repository<User>
) {}
async getAllUsers (): Promise<User[]> {
return await this.usersRepository.find({})
}
async getOneUser (username: string): Promise<User | null> {
return await this.usersRepository.findOneBy({ username })
}
async getOneUser42 (id_42: number): Promise<User | null> {
return await this.usersRepository.findOneBy({ id_42 })
}
async create (newUser: CreateUserDto) {
try {
const user = new User()
user.id_42 = newUser.id_42
user.avatar = newUser.avatar
user.username = newUser.username
return await this.usersRepository.save(user)
} catch (err) {
throw new Error(`Error creating ${err} user ${err.message}`)
}
}
async findOne (id: number) {
const user = await this.usersRepository.findOneBy({ id })
if (user == null) {
throw new NotFoundException(`User #${id} not found`)
}
return user
}
async update (id: number, changes: UpdateUserDto) {
const user = await this.findOne(id)
await this.usersRepository.merge(user, changes)
return await this.usersRepository.save(user)
}
}

3
back/volume/tsconfig.json

@ -12,6 +12,7 @@
"baseUrl": "./", "baseUrl": "./",
"incremental": true, "incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true "alwaysStrict": true,
"noImplicitAny": true
} }
} }

1
docker-compose.yml

@ -30,6 +30,7 @@ services:
container_name: postgres container_name: postgres
image: postgres image: postgres
ports: [5432:5432] ports: [5432:5432]
networks: [transcendence]
restart: always restart: always
env_file: .env env_file: .env

1
front/Dockerfile

@ -1,6 +1,7 @@
FROM alpine:3.15 FROM alpine:3.15
RUN apk update && apk upgrade && apk add npm RUN apk update && apk upgrade && apk add npm
WORKDIR /var/www/html WORKDIR /var/www/html
ENTRYPOINT npm install; \ ENTRYPOINT npm install; \

6
package-lock.json

@ -0,0 +1,6 @@
{
"name": "transcendance",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
Loading…
Cancel
Save