Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f591cd60 | ||
|
|
f836a0a3cd | ||
|
|
11174656e4 | ||
|
|
faea1abcc4 | ||
|
|
71658ad39d | ||
|
|
167f0f8c7a |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,3 +1,16 @@
|
|||||||
|
### [0.5.1](https://github.com/stonith404/pingvin-share/compare/v0.5.0...v0.5.1) (2023-01-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* show version and show button if new release is available on admin page ([71658ad](https://github.com/stonith404/pingvin-share/commit/71658ad39d7e3638de659e8230fad4e05f60fdd8))
|
||||||
|
* use cookies for authentication ([faea1ab](https://github.com/stonith404/pingvin-share/commit/faea1abcc4b533f391feaed427e211fef9166fe4))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* email configuration updated without restart ([1117465](https://github.com/stonith404/pingvin-share/commit/11174656e425c4be60e4f7b1ea8463678e5c60d2))
|
||||||
|
|
||||||
## [0.5.0](https://github.com/stonith404/pingvin-share/compare/v0.4.0...v0.5.0) (2022-12-30)
|
## [0.5.0](https://github.com/stonith404/pingvin-share/compare/v0.4.0...v0.5.0) (2022-12-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
60
backend/package-lock.json
generated
60
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^9.2.1",
|
"@nestjs/common": "^9.2.1",
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
"@nestjs/schematics": "^9.0.3",
|
"@nestjs/schematics": "^9.0.3",
|
||||||
"@nestjs/testing": "^9.2.1",
|
"@nestjs/testing": "^9.2.1",
|
||||||
"@types/archiver": "^5.3.1",
|
"@types/archiver": "^5.3.1",
|
||||||
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/mime-types": "^2.1.1",
|
"@types/mime-types": "^2.1.1",
|
||||||
@@ -1151,6 +1153,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie-parser": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/cookiejar": {
|
"node_modules/@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
@@ -2635,6 +2646,26 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
@@ -8413,6 +8444,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/cookie-parser": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/cookiejar": {
|
"@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
@@ -9570,6 +9610,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||||
},
|
},
|
||||||
|
"cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"dev": "nest start --watch",
|
"dev": "nest start --watch",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
"content-disposition": "^0.5.4",
|
"content-disposition": "^0.5.4",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
"@nestjs/schematics": "^9.0.3",
|
"@nestjs/schematics": "^9.0.3",
|
||||||
"@nestjs/testing": "^9.2.1",
|
"@nestjs/testing": "^9.2.1",
|
||||||
"@types/archiver": "^5.3.1",
|
"@types/archiver": "^5.3.1",
|
||||||
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/mime-types": "^2.1.1",
|
"@types/mime-types": "^2.1.1",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `RefreshToken` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- The required column `id` was added to the `RefreshToken` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_RefreshToken" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"token" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"expiresAt" DATETIME NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_RefreshToken" ("createdAt", "expiresAt", "token", "userId") SELECT "createdAt", "expiresAt", "token", "userId" FROM "RefreshToken";
|
||||||
|
DROP TABLE "RefreshToken";
|
||||||
|
ALTER TABLE "new_RefreshToken" RENAME TO "RefreshToken";
|
||||||
|
CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -27,7 +27,8 @@ model User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model RefreshToken {
|
model RefreshToken {
|
||||||
token String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
token String @unique @default(uuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
Patch,
|
Patch,
|
||||||
Post,
|
Post,
|
||||||
|
Req,
|
||||||
|
Res,
|
||||||
|
UnauthorizedException,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { Throttle } from "@nestjs/throttler";
|
import { Throttle } from "@nestjs/throttler";
|
||||||
import { User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
|
import { Request, Response } from "express";
|
||||||
import { ConfigService } from "src/config/config.service";
|
import { ConfigService } from "src/config/config.service";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { AuthTotpService } from "./authTotp.service";
|
import { AuthTotpService } from "./authTotp.service";
|
||||||
@@ -17,7 +21,6 @@ import { AuthRegisterDTO } from "./dto/authRegister.dto";
|
|||||||
import { AuthSignInDTO } from "./dto/authSignIn.dto";
|
import { AuthSignInDTO } from "./dto/authSignIn.dto";
|
||||||
import { AuthSignInTotpDTO } from "./dto/authSignInTotp.dto";
|
import { AuthSignInTotpDTO } from "./dto/authSignInTotp.dto";
|
||||||
import { EnableTotpDTO } from "./dto/enableTotp.dto";
|
import { EnableTotpDTO } from "./dto/enableTotp.dto";
|
||||||
import { RefreshAccessTokenDTO } from "./dto/refreshAccessToken.dto";
|
|
||||||
import { UpdatePasswordDTO } from "./dto/updatePassword.dto";
|
import { UpdatePasswordDTO } from "./dto/updatePassword.dto";
|
||||||
import { VerifyTotpDTO } from "./dto/verifyTotp.dto";
|
import { VerifyTotpDTO } from "./dto/verifyTotp.dto";
|
||||||
import { JwtGuard } from "./guard/jwt.guard";
|
import { JwtGuard } from "./guard/jwt.guard";
|
||||||
@@ -32,24 +35,59 @@ export class AuthController {
|
|||||||
|
|
||||||
@Throttle(10, 5 * 60)
|
@Throttle(10, 5 * 60)
|
||||||
@Post("signUp")
|
@Post("signUp")
|
||||||
async signUp(@Body() dto: AuthRegisterDTO) {
|
async signUp(
|
||||||
|
@Body() dto: AuthRegisterDTO,
|
||||||
|
@Res({ passthrough: true }) response: Response
|
||||||
|
) {
|
||||||
if (!this.config.get("ALLOW_REGISTRATION"))
|
if (!this.config.get("ALLOW_REGISTRATION"))
|
||||||
throw new ForbiddenException("Registration is not allowed");
|
throw new ForbiddenException("Registration is not allowed");
|
||||||
return this.authService.signUp(dto);
|
const result = await this.authService.signUp(dto);
|
||||||
|
|
||||||
|
response = this.addTokensToResponse(
|
||||||
|
response,
|
||||||
|
result.accessToken,
|
||||||
|
result.refreshToken
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throttle(10, 5 * 60)
|
@Throttle(10, 5 * 60)
|
||||||
@Post("signIn")
|
@Post("signIn")
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
signIn(@Body() dto: AuthSignInDTO) {
|
async signIn(
|
||||||
return this.authService.signIn(dto);
|
@Body() dto: AuthSignInDTO,
|
||||||
|
@Res({ passthrough: true }) response: Response
|
||||||
|
) {
|
||||||
|
const result = await this.authService.signIn(dto);
|
||||||
|
|
||||||
|
if (result.accessToken && result.refreshToken) {
|
||||||
|
response = this.addTokensToResponse(
|
||||||
|
response,
|
||||||
|
result.accessToken,
|
||||||
|
result.refreshToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throttle(10, 5 * 60)
|
@Throttle(10, 5 * 60)
|
||||||
@Post("signIn/totp")
|
@Post("signIn/totp")
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
signInTotp(@Body() dto: AuthSignInTotpDTO) {
|
async signInTotp(
|
||||||
return this.authTotpService.signInTotp(dto);
|
@Body() dto: AuthSignInTotpDTO,
|
||||||
|
@Res({ passthrough: true }) response: Response
|
||||||
|
) {
|
||||||
|
const result = await this.authTotpService.signInTotp(dto);
|
||||||
|
|
||||||
|
response = this.addTokensToResponse(
|
||||||
|
response,
|
||||||
|
result.accessToken,
|
||||||
|
result.refreshToken
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("password")
|
@Patch("password")
|
||||||
@@ -60,13 +98,33 @@ export class AuthController {
|
|||||||
|
|
||||||
@Post("token")
|
@Post("token")
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async refreshAccessToken(@Body() body: RefreshAccessTokenDTO) {
|
async refreshAccessToken(
|
||||||
|
@Req() request: Request,
|
||||||
|
@Res({ passthrough: true }) response: Response
|
||||||
|
) {
|
||||||
|
if (!request.cookies.refresh_token) throw new UnauthorizedException();
|
||||||
|
|
||||||
const accessToken = await this.authService.refreshAccessToken(
|
const accessToken = await this.authService.refreshAccessToken(
|
||||||
body.refreshToken
|
request.cookies.refresh_token
|
||||||
);
|
);
|
||||||
|
response.cookie("access_token", accessToken, { httpOnly: true });
|
||||||
return { accessToken };
|
return { accessToken };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post("signOut")
|
||||||
|
async signOut(
|
||||||
|
@Req() request: Request,
|
||||||
|
@Res({ passthrough: true }) response: Response
|
||||||
|
) {
|
||||||
|
await this.authService.signOut(request.cookies.access_token);
|
||||||
|
response.cookie("access_token", "accessToken", { maxAge: -1 });
|
||||||
|
response.cookie("refresh_token", "", {
|
||||||
|
path: "/api/auth/token",
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: -1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Post("totp/enable")
|
@Post("totp/enable")
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
async enableTotp(@GetUser() user: User, @Body() body: EnableTotpDTO) {
|
async enableTotp(@GetUser() user: User, @Body() body: EnableTotpDTO) {
|
||||||
@@ -85,4 +143,19 @@ export class AuthController {
|
|||||||
// Note: We use VerifyTotpDTO here because it has both fields we need: password and totp code
|
// Note: We use VerifyTotpDTO here because it has both fields we need: password and totp code
|
||||||
return this.authTotpService.disableTotp(user, body.password, body.code);
|
return this.authTotpService.disableTotp(user, body.password, body.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addTokensToResponse(
|
||||||
|
response: Response,
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string
|
||||||
|
) {
|
||||||
|
response.cookie("access_token", accessToken);
|
||||||
|
response.cookie("refresh_token", refreshToken, {
|
||||||
|
path: "/api/auth/token",
|
||||||
|
httpOnly: true,
|
||||||
|
maxAge: 60 * 60 * 24 * 30 * 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ export class AuthService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessToken = await this.createAccessToken(user);
|
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
|
||||||
const refreshToken = await this.createRefreshToken(user.id);
|
user.id
|
||||||
|
);
|
||||||
|
const accessToken = await this.createAccessToken(user, refreshTokenId);
|
||||||
|
|
||||||
return { accessToken, refreshToken };
|
return { accessToken, refreshToken };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -71,8 +73,10 @@ export class AuthService {
|
|||||||
return { loginToken };
|
return { loginToken };
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessToken = await this.createAccessToken(user);
|
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
|
||||||
const refreshToken = await this.createRefreshToken(user.id);
|
user.id
|
||||||
|
);
|
||||||
|
const accessToken = await this.createAccessToken(user, refreshTokenId);
|
||||||
|
|
||||||
return { accessToken, refreshToken };
|
return { accessToken, refreshToken };
|
||||||
}
|
}
|
||||||
@@ -89,11 +93,12 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createAccessToken(user: User) {
|
async createAccessToken(user: User, refreshTokenId: string) {
|
||||||
return this.jwtService.sign(
|
return this.jwtService.sign(
|
||||||
{
|
{
|
||||||
sub: user.id,
|
sub: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
refreshTokenId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expiresIn: "15min",
|
expiresIn: "15min",
|
||||||
@@ -102,6 +107,14 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signOut(accessToken: string) {
|
||||||
|
const { refreshTokenId } = this.jwtService.decode(accessToken) as {
|
||||||
|
refreshTokenId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.prisma.refreshToken.delete({ where: { id: refreshTokenId } });
|
||||||
|
}
|
||||||
|
|
||||||
async refreshAccessToken(refreshToken: string) {
|
async refreshAccessToken(refreshToken: string) {
|
||||||
const refreshTokenMetaData = await this.prisma.refreshToken.findUnique({
|
const refreshTokenMetaData = await this.prisma.refreshToken.findUnique({
|
||||||
where: { token: refreshToken },
|
where: { token: refreshToken },
|
||||||
@@ -111,17 +124,18 @@ export class AuthService {
|
|||||||
if (!refreshTokenMetaData || refreshTokenMetaData.expiresAt < new Date())
|
if (!refreshTokenMetaData || refreshTokenMetaData.expiresAt < new Date())
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
|
|
||||||
return this.createAccessToken(refreshTokenMetaData.user);
|
return this.createAccessToken(
|
||||||
|
refreshTokenMetaData.user,
|
||||||
|
refreshTokenMetaData.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRefreshToken(userId: string) {
|
async createRefreshToken(userId: string) {
|
||||||
const refreshToken = (
|
const { id, token } = await this.prisma.refreshToken.create({
|
||||||
await this.prisma.refreshToken.create({
|
data: { userId, expiresAt: moment().add(3, "months").toDate() },
|
||||||
data: { userId, expiresAt: moment().add(3, "months").toDate() },
|
});
|
||||||
})
|
|
||||||
).token;
|
|
||||||
|
|
||||||
return refreshToken;
|
return { refreshTokenId: id, refreshToken: token };
|
||||||
}
|
}
|
||||||
|
|
||||||
async createLoginToken(userId: string) {
|
async createLoginToken(userId: string) {
|
||||||
|
|||||||
@@ -71,8 +71,12 @@ export class AuthTotpService {
|
|||||||
data: { used: true },
|
data: { used: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessToken = await this.authService.createAccessToken(user);
|
const { refreshToken, refreshTokenId } =
|
||||||
const refreshToken = await this.authService.createRefreshToken(user.id);
|
await this.authService.createRefreshToken(user.id);
|
||||||
|
const accessToken = await this.authService.createAccessToken(
|
||||||
|
user,
|
||||||
|
refreshTokenId
|
||||||
|
);
|
||||||
|
|
||||||
return { accessToken, refreshToken };
|
return { accessToken, refreshToken };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { IsNotEmpty } from "class-validator";
|
|
||||||
|
|
||||||
export class RefreshAccessTokenDTO {
|
|
||||||
@IsNotEmpty()
|
|
||||||
refreshToken: string;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PassportStrategy } from "@nestjs/passport";
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
import { User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
import { ExtractJwt, Strategy } from "passport-jwt";
|
import { Request } from "express";
|
||||||
|
import { Strategy } from "passport-jwt";
|
||||||
import { ConfigService } from "src/config/config.service";
|
import { ConfigService } from "src/config/config.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
@@ -10,11 +11,16 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||||||
constructor(config: ConfigService, private prisma: PrismaService) {
|
constructor(config: ConfigService, private prisma: PrismaService) {
|
||||||
config.get("JWT_SECRET");
|
config.get("JWT_SECRET");
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: JwtStrategy.extractJWT,
|
||||||
secretOrKey: config.get("JWT_SECRET"),
|
secretOrKey: config.get("JWT_SECRET"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static extractJWT(req: Request) {
|
||||||
|
if (!req.cookies.access_token) return null;
|
||||||
|
return req.cookies.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
async validate(payload: { sub: string }) {
|
async validate(payload: { sub: string }) {
|
||||||
const user: User = await this.prisma.user.findUnique({
|
const user: User = await this.prisma.user.findUnique({
|
||||||
where: { id: payload.sub },
|
where: { id: payload.sub },
|
||||||
|
|||||||
@@ -7,15 +7,17 @@ import { ConfigService } from "src/config/config.service";
|
|||||||
export class EmailService {
|
export class EmailService {
|
||||||
constructor(private config: ConfigService) {}
|
constructor(private config: ConfigService) {}
|
||||||
|
|
||||||
transporter = nodemailer.createTransport({
|
getTransporter() {
|
||||||
host: this.config.get("SMTP_HOST"),
|
return nodemailer.createTransport({
|
||||||
port: parseInt(this.config.get("SMTP_PORT")),
|
host: this.config.get("SMTP_HOST"),
|
||||||
secure: parseInt(this.config.get("SMTP_PORT")) == 465,
|
port: parseInt(this.config.get("SMTP_PORT")),
|
||||||
auth: {
|
secure: parseInt(this.config.get("SMTP_PORT")) == 465,
|
||||||
user: this.config.get("SMTP_USERNAME"),
|
auth: {
|
||||||
pass: this.config.get("SMTP_PASSWORD"),
|
user: this.config.get("SMTP_USERNAME"),
|
||||||
},
|
pass: this.config.get("SMTP_PASSWORD"),
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async sendMail(recipientEmail: string, shareId: string, creator: User) {
|
async sendMail(recipientEmail: string, shareId: string, creator: User) {
|
||||||
if (!this.config.get("ENABLE_EMAIL_RECIPIENTS"))
|
if (!this.config.get("ENABLE_EMAIL_RECIPIENTS"))
|
||||||
@@ -23,7 +25,7 @@ export class EmailService {
|
|||||||
|
|
||||||
const shareUrl = `${this.config.get("APP_URL")}/share/${shareId}`;
|
const shareUrl = `${this.config.get("APP_URL")}/share/${shareId}`;
|
||||||
|
|
||||||
await this.transporter.sendMail({
|
await this.getTransporter().sendMail({
|
||||||
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
|
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
|
||||||
to: recipientEmail,
|
to: recipientEmail,
|
||||||
subject: this.config.get("EMAIL_SUBJECT"),
|
subject: this.config.get("EMAIL_SUBJECT"),
|
||||||
@@ -36,7 +38,7 @@ export class EmailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendTestMail(recipientEmail: string) {
|
async sendTestMail(recipientEmail: string) {
|
||||||
await this.transporter.sendMail({
|
await this.getTransporter().sendMail({
|
||||||
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
|
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
|
||||||
to: recipientEmail,
|
to: recipientEmail,
|
||||||
subject: "Test email",
|
subject: "Test email",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ClassSerializerInterceptor, ValidationPipe } from "@nestjs/common";
|
import { ClassSerializerInterceptor, ValidationPipe } from "@nestjs/common";
|
||||||
import { NestFactory, Reflector } from "@nestjs/core";
|
import { NestFactory, Reflector } from "@nestjs/core";
|
||||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||||
|
import * as cookieParser from "cookie-parser";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ async function bootstrap() {
|
|||||||
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
|
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
|
||||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
||||||
|
|
||||||
|
app.use(cookieParser());
|
||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
|
|
||||||
await fs.promises.mkdir("./data/uploads/_temp", { recursive: true });
|
await fs.promises.mkdir("./data/uploads/_temp", { recursive: true });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "84a95987-2997-429a-aba6-d38289b0b76a",
|
"_postman_id": "4b16228d-41ef-4c6b-8a0b-294a30a4cfc2",
|
||||||
"name": "Pingvin Share Testing",
|
"name": "Pingvin Share Testing",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
"_exporter_id": "17822132"
|
"_exporter_id": "17822132"
|
||||||
@@ -18,12 +18,12 @@
|
|||||||
"exec": [
|
"exec": [
|
||||||
"if(pm.response.to.have.status(201)){",
|
"if(pm.response.to.have.status(201)){",
|
||||||
" const token = pm.response.json()[\"accessToken\"]",
|
" const token = pm.response.json()[\"accessToken\"]",
|
||||||
" pm.collectionVariables.set(\"USER_AUTH_TOKEN\", token)",
|
|
||||||
"",
|
|
||||||
" // Get user id",
|
" // Get user id",
|
||||||
" const jwtPayload = JSON.parse(atob(token.split('.')[1]));",
|
" const jwtPayload = JSON.parse(atob(token.split('.')[1]));",
|
||||||
" const userId = jwtPayload[\"sub\"]",
|
" const userId = jwtPayload[\"sub\"]",
|
||||||
" pm.collectionVariables.set(\"USER_ID\", userId)",
|
" pm.collectionVariables.set(\"USER_ID\", userId)",
|
||||||
|
"",
|
||||||
|
" pm.collectionVariables.set(\"COOKIES\", pm.response.headers.get(\"Set-Cookie\"))",
|
||||||
"}",
|
"}",
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
@@ -80,6 +80,7 @@
|
|||||||
" pm.expect(responseBody).to.have.property(\"accessToken\")",
|
" pm.expect(responseBody).to.have.property(\"accessToken\")",
|
||||||
" pm.expect(responseBody).to.have.property(\"refreshToken\")",
|
" pm.expect(responseBody).to.have.property(\"refreshToken\")",
|
||||||
"});",
|
"});",
|
||||||
|
"",
|
||||||
""
|
""
|
||||||
],
|
],
|
||||||
"type": "text/javascript"
|
"type": "text/javascript"
|
||||||
@@ -97,7 +98,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"email\": \"system2@test.org\",\n \"username\": \"system.test2\",\n \"password\": \"N44HcHgeuAvfCT\"\n}",
|
"raw": "{\n \"email\": \"system2@test.org\",\n \"username\": \"system2.test\",\n \"password\": \"N44HcHgeuAvfCT\"\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@@ -1556,23 +1557,13 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"auth": {
|
|
||||||
"type": "bearer",
|
|
||||||
"bearer": [
|
|
||||||
{
|
|
||||||
"key": "token",
|
|
||||||
"value": "{{USER_AUTH_TOKEN}}",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"event": [
|
"event": [
|
||||||
{
|
{
|
||||||
"listen": "prerequest",
|
"listen": "prerequest",
|
||||||
"script": {
|
"script": {
|
||||||
"type": "text/javascript",
|
"type": "text/javascript",
|
||||||
"exec": [
|
"exec": [
|
||||||
""
|
"pm.request.addHeader(\"Cookie\", pm.collectionVariables.get(\"COOKIES\"))"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
const { version } = require('./package.json');
|
||||||
|
|
||||||
const withPWA = require("next-pwa")({
|
const withPWA = require("next-pwa")({
|
||||||
dest: "public",
|
dest: "public",
|
||||||
disable: process.env.NODE_ENV == "development",
|
disable: process.env.NODE_ENV == "development",
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = withPWA({ output: "standalone" });
|
module.exports = withPWA({
|
||||||
|
output: "standalone", env: {
|
||||||
|
VERSION: version,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.0.1",
|
"version": "0.5.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const AdminConfigInput = ({
|
|||||||
(configVariable.obscured ? (
|
(configVariable.obscured ? (
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onChange={(e) => onValueChange(configVariable, e.target.value)}
|
|
||||||
{...form.getInputProps("stringValue")}
|
{...form.getInputProps("stringValue")}
|
||||||
|
onChange={(e) => onValueChange(configVariable, e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const ActionAvatar = () => {
|
|||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
authService.signOut();
|
await authService.signOut();
|
||||||
}}
|
}}
|
||||||
icon={<TbDoorExit size={14} />}
|
icon={<TbDoorExit size={14} />}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ function App({ Component, pageProps }: AppProps) {
|
|||||||
const systemTheme = useColorScheme();
|
const systemTheme = useColorScheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const preferences = usePreferences();
|
const preferences = usePreferences();
|
||||||
|
|
||||||
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
|
const [colorScheme, setColorScheme] = useState<ColorScheme>("light");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [user, setUser] = useState<CurrentUser | null>(null);
|
const [user, setUser] = useState<CurrentUser | null>(null);
|
||||||
@@ -89,7 +88,7 @@ function App({ Component, pageProps }: AppProps) {
|
|||||||
<Container>
|
<Container>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Container>
|
</Container>
|
||||||
</UserContext.Provider>{" "}
|
</UserContext.Provider>
|
||||||
</ConfigContext.Provider>
|
</ConfigContext.Provider>
|
||||||
)}
|
)}
|
||||||
</GlobalLoadingContext.Provider>
|
</GlobalLoadingContext.Provider>
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { Col, createStyles, Grid, Paper, Text } from "@mantine/core";
|
import {
|
||||||
|
Center,
|
||||||
|
Col,
|
||||||
|
createStyles,
|
||||||
|
Grid,
|
||||||
|
Paper,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from "@mantine/core";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { TbSettings, TbUsers } from "react-icons/tb";
|
import { useEffect, useState } from "react";
|
||||||
|
import { TbRefresh, TbSettings, TbUsers } from "react-icons/tb";
|
||||||
const managementOptions = [
|
import configService from "../../services/config.service";
|
||||||
{
|
|
||||||
title: "User management",
|
|
||||||
icon: TbUsers,
|
|
||||||
route: "/admin/users",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Configuration",
|
|
||||||
icon: TbSettings,
|
|
||||||
route: "/admin/config",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
item: {
|
item: {
|
||||||
@@ -33,27 +31,69 @@ const useStyles = createStyles((theme) => ({
|
|||||||
const Admin = () => {
|
const Admin = () => {
|
||||||
const { classes, theme } = useStyles();
|
const { classes, theme } = useStyles();
|
||||||
|
|
||||||
|
const [managementOptions, setManagementOptions] = useState([
|
||||||
|
{
|
||||||
|
title: "User management",
|
||||||
|
icon: TbUsers,
|
||||||
|
route: "/admin/users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Configuration",
|
||||||
|
icon: TbSettings,
|
||||||
|
route: "/admin/config",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
configService.isNewReleaseAvailable().then((isNewReleaseAvailable) => {
|
||||||
|
if (isNewReleaseAvailable) {
|
||||||
|
setManagementOptions([
|
||||||
|
...managementOptions,
|
||||||
|
{
|
||||||
|
title: "Update",
|
||||||
|
icon: TbRefresh,
|
||||||
|
route:
|
||||||
|
"https://github.com/stonith404/pingvin-share/releases/tag/v0.5.0",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p={40}>
|
<>
|
||||||
<Grid mt="md">
|
<Title mb={30} order={3}>
|
||||||
{managementOptions.map((item) => {
|
Administration
|
||||||
return (
|
</Title>
|
||||||
<Col xs={6} key={item.route}>
|
<Stack justify="space-between" style={{ height: "calc(100vh - 180px)" }}>
|
||||||
<Paper
|
<Paper withBorder p={40}>
|
||||||
withBorder
|
<Grid>
|
||||||
component={Link}
|
{managementOptions.map((item) => {
|
||||||
href={item.route}
|
return (
|
||||||
key={item.title}
|
<Col xs={6} key={item.route}>
|
||||||
className={classes.item}
|
<Paper
|
||||||
>
|
withBorder
|
||||||
<item.icon color={theme.colors.victoria[8]} size={35} />
|
component={Link}
|
||||||
<Text mt={7}>{item.title}</Text>
|
href={item.route}
|
||||||
</Paper>
|
key={item.title}
|
||||||
</Col>
|
className={classes.item}
|
||||||
);
|
>
|
||||||
})}
|
<item.icon color={theme.colors.victoria[8]} size={35} />
|
||||||
</Grid>
|
<Text mt={7}>{item.title}</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Center>
|
||||||
|
<Text size="xs" color="dimmed">
|
||||||
|
Version {process.env.VERSION}
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import axios, { AxiosError } from "axios";
|
import axios from "axios";
|
||||||
import { getCookie } from "cookies-next";
|
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: "/api",
|
baseURL: "/api",
|
||||||
});
|
});
|
||||||
|
|
||||||
api.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
const accessToken = getCookie("access_token");
|
|
||||||
if (accessToken) {
|
|
||||||
config!.headers!.Authorization = `Bearer ${accessToken}`;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error: AxiosError) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getCookie, setCookie } from "cookies-next";
|
import { getCookie } from "cookies-next";
|
||||||
import * as jose from "jose";
|
import * as jose from "jose";
|
||||||
import api from "./api.service";
|
import api from "./api.service";
|
||||||
|
|
||||||
@@ -12,11 +12,6 @@ const signIn = async (emailOrUsername: string, password: string) => {
|
|||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
setCookie("access_token", response.data.accessToken);
|
|
||||||
setCookie("refresh_token", response.data.refreshToken, {
|
|
||||||
maxAge: 60 * 60 * 24 * 30 * 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,45 +32,30 @@ const signInTotp = async (
|
|||||||
loginToken,
|
loginToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
setCookie("access_token", response.data.accessToken);
|
|
||||||
setCookie("refresh_token", response.data.refreshToken, {
|
|
||||||
maxAge: 60 * 60 * 24 * 30 * 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const signUp = async (email: string, username: string, password: string) => {
|
const signUp = async (email: string, username: string, password: string) => {
|
||||||
const response = await api.post("auth/signUp", { email, username, password });
|
const response = await api.post("auth/signUp", { email, username, password });
|
||||||
|
|
||||||
setCookie("access_token", response.data.accessToken);
|
|
||||||
setCookie("refresh_token", response.data.refreshToken, {
|
|
||||||
maxAge: 60 * 60 * 24 * 30 * 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
const signOut = () => {
|
const signOut = async () => {
|
||||||
setCookie("access_token", null);
|
await api.post("/auth/signOut");
|
||||||
setCookie("refresh_token", null);
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshAccessToken = async () => {
|
const refreshAccessToken = async () => {
|
||||||
try {
|
try {
|
||||||
const accessToken = getCookie("access_token") as string;
|
const accessToken = getCookie("access_token") as string;
|
||||||
const refreshToken = getCookie("refresh_token");
|
|
||||||
if (
|
if (
|
||||||
(accessToken &&
|
!accessToken ||
|
||||||
(jose.decodeJwt(accessToken).exp ?? 0) * 1000 <
|
(jose.decodeJwt(accessToken).exp ?? 0) * 1000 < Date.now() + 2 * 60 * 1000
|
||||||
Date.now() + 2 * 60 * 1000) ||
|
|
||||||
(refreshToken && !accessToken)
|
|
||||||
) {
|
) {
|
||||||
const response = await api.post("auth/token", { refreshToken });
|
await api.post("/auth/token");
|
||||||
setCookie("access_token", response.data.accessToken);
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (e) {
|
||||||
console.info("Refresh token invalid or expired");
|
console.info("Refresh token invalid or expired");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import axios from "axios";
|
||||||
import Config, { AdminConfig, UpdateConfig } from "../types/config.type";
|
import Config, { AdminConfig, UpdateConfig } from "../types/config.type";
|
||||||
import api from "./api.service";
|
import api from "./api.service";
|
||||||
|
|
||||||
@@ -36,6 +37,15 @@ const sendTestEmail = async (email: string) => {
|
|||||||
await api.post("/configs/admin/testEmail", { email });
|
await api.post("/configs/admin/testEmail", { email });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isNewReleaseAvailable = async () => {
|
||||||
|
const response = (
|
||||||
|
await axios.get(
|
||||||
|
"https://api.github.com/repos/stonith404/pingvin-share/releases/latest"
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
return response.tag_name.replace("v", "") != process.env.VERSION;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
list,
|
list,
|
||||||
listForAdmin,
|
listForAdmin,
|
||||||
@@ -43,4 +53,5 @@ export default {
|
|||||||
get,
|
get,
|
||||||
finishSetup,
|
finishSetup,
|
||||||
sendTestEmail,
|
sendTestEmail,
|
||||||
|
isNewReleaseAvailable,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||||
"version": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s && git add CHANGELOG.md",
|
"version": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s && git add CHANGELOG.md",
|
||||||
"release:patch": "npm version patch -m 'release: %s' && git push && git push --tags",
|
"release:patch": "cd backend && npm version patch --commit-hooks false && cd ../frontend && npm version patch --commit-hooks false && cd .. && git add . && npm version patch --force -m 'release: %s' && git push && git push --tags",
|
||||||
"release:minor": "npm version minor -m 'release: %s' && git push && git push --tags",
|
"release:minor": "cd backend && npm version minor --commit-hooks false && cd ../frontend && npm version minor --commit-hooks false && cd .. && git add . && npm version minor --force -m 'release: %s' && git push && git push --tags",
|
||||||
"deploy:dev": "docker buildx build --push --tag stonith404/pingvin-share:development --platform linux/amd64,linux/arm64 ."
|
"deploy:dev": "docker buildx build --push --tag stonith404/pingvin-share:development --platform linux/amd64,linux/arm64 ."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user