(wip: http basic auth)
This commit is contained in:
parent
81f86acc7b
commit
1fdd55e825
4 changed files with 877 additions and 451 deletions
|
|
@ -19,27 +19,32 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"packageManager": "pnpm@10.11.0",
|
"packageManager": "pnpm@10.11.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@feathersjs/feathers": "^5.0.6",
|
"@feathersjs/feathers": "^5.0.49",
|
||||||
"@google-cloud/local-auth": "^3.0.1",
|
"@google-cloud/local-auth": "^3.0.1",
|
||||||
"@repo/common": "workspace:*",
|
"@repo/common": "workspace:*",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"csv-parser": "^3.2.0",
|
"csv-parser": "^3.2.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.2.1",
|
||||||
"google-auth-library": "^9.15.1",
|
"google-auth-library": "^9.15.1",
|
||||||
"googleapis": "^149.0.0",
|
"googleapis": "^149.0.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
|
"passport-http": "^0.3.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typeorm": "0.3.26",
|
"typeorm": "0.3.26",
|
||||||
"zod": "^4.1.8"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/ts-reset": "^0.6.1",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/cors": "^2.8.19",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.3",
|
"@types/express": "^5.0.6",
|
||||||
"@types/node": "^22.5.1",
|
"@types/node": "^22.20.1",
|
||||||
"tsx": "^4.19.4",
|
"@types/passport": "^1.0.17",
|
||||||
"turbo": "^2.8.0"
|
"@types/passport-http": "^0.3.11",
|
||||||
|
"tsx": "^4.23.12",
|
||||||
|
"turbo": "^2.10.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,16 @@ import {
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
JoinTable,
|
JoinTable,
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
|
import * as bcrypt from 'bcrypt';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Lemma extends BaseEntity {
|
export class Lemma extends BaseEntity {
|
||||||
@PrimaryColumn({ type: "text" })
|
@PrimaryColumn({ type: "text" })
|
||||||
lemma_name: string;
|
lemma_name: string;
|
||||||
|
|
||||||
@OneToMany(() => WordForm, (word_form) => word_form.lemma, { cascade: true})
|
@OneToMany(() => WordForm, (word_form) => word_form.lemma, {
|
||||||
|
cascade: true,
|
||||||
|
})
|
||||||
word_forms: WordForm[];
|
word_forms: WordForm[];
|
||||||
|
|
||||||
@OneToMany(() => Example, (example) => example.lemma)
|
@OneToMany(() => Example, (example) => example.lemma)
|
||||||
|
|
@ -119,21 +122,39 @@ export class PartOfSpeech extends BaseEntity {
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class User extends BaseEntity {
|
export class User extends BaseEntity {
|
||||||
@PrimaryColumn({ type: "text", nullable: false })
|
@PrimaryGeneratedColumn("uuid")
|
||||||
|
user_id: string;
|
||||||
|
|
||||||
|
@Column({ type: "text", nullable: false })
|
||||||
user_name: string;
|
user_name: string;
|
||||||
|
|
||||||
@ManyToMany(() => Role, (role) => role.users, {eager:true})
|
@Column({ type: "text", nullable: false })
|
||||||
|
password_hash: string;
|
||||||
|
|
||||||
|
@ManyToMany(() => Role, (role) => role.users, { eager: true })
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
|
||||||
|
setPassword(plainPassword: string) {
|
||||||
|
this.password_hash = bcrypt.hashSync(plainPassword, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPassword(plainPassword: string) {
|
||||||
|
return bcrypt.compareSync(plainPassword, this.password_hash);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Role extends BaseEntity {
|
export class Role extends BaseEntity {
|
||||||
@PrimaryColumn({ type: "text", nullable: false})
|
@PrimaryGeneratedColumn("uuid")
|
||||||
|
role_id: string;
|
||||||
|
|
||||||
|
@Column({ type: "text", nullable: false })
|
||||||
role_name: string;
|
role_name: string;
|
||||||
|
|
||||||
@ManyToMany(() => User, (user) => user.roles, {eager:true})
|
@ManyToMany(() => User, (user) => user.roles)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
users: User[];
|
users: User[];
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import { appDataSource } from "./config/dbconfig.js";
|
||||||
import { Lemma, WordForm, Lect, Definition, Example, Role, User } from "./db/dbmodel.js";
|
import { Lemma, WordForm, Lect, Definition, Example, Role, User } from "./db/dbmodel.js";
|
||||||
import "@total-typescript/ts-reset";
|
import "@total-typescript/ts-reset";
|
||||||
import { Like, In } from "typeorm";
|
import { Like, In } from "typeorm";
|
||||||
import cors from "cors";
|
import { BasicStrategy } from "passport-http";
|
||||||
|
import passport from "passport";
|
||||||
|
|
||||||
const RELOAD_SHEET_ON_START = false;
|
const RELOAD_SHEET_ON_START = false;
|
||||||
const SOURCE_FILE = "res/sample.tsv";
|
const SOURCE_FILE = "res/sample.tsv";
|
||||||
|
|
@ -22,77 +23,62 @@ appDataSource
|
||||||
|
|
||||||
if (RELOAD_SHEET_ON_START) {
|
if (RELOAD_SHEET_ON_START) {
|
||||||
await loadSheet();
|
await loadSheet();
|
||||||
await ensureDefaultRoles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ensureDefaultRoles();
|
||||||
})
|
})
|
||||||
.catch((error) => console.log(error));
|
.catch((error) => console.log(error));
|
||||||
|
|
||||||
async function ensureDefaultRoles(){
|
async function ensureDefaultRoles(){
|
||||||
const ADMIN_NAME = "admin"
|
const ADMIN_NAME = "admin";
|
||||||
const EDITOR_NAME = "editor"
|
const EDITOR_NAME = "editor";
|
||||||
const READER_NAME = "reader"
|
const READER_NAME = "reader";
|
||||||
|
|
||||||
let admin_role = new Role();
|
|
||||||
admin_role.role_name = ADMIN_NAME;
|
|
||||||
|
|
||||||
let editor_role = new Role();
|
let admin_role: Role;
|
||||||
editor_role.role_name = EDITOR_NAME;
|
let editor_role: Role;
|
||||||
|
let reader_role: Role;
|
||||||
|
|
||||||
let reader_role = new Role();
|
// Initialize roles if they don't exist yet
|
||||||
reader_role.role_name = READER_NAME;
|
try {
|
||||||
|
[admin_role, editor_role, reader_role] = await Promise.all([
|
||||||
|
Role.findOneBy({ role_name: ADMIN_NAME }).then(r => r || Role.save({ role_name: ADMIN_NAME })),
|
||||||
|
Role.findOneBy({ role_name: EDITOR_NAME }).then(r => r || Role.save({ role_name: EDITOR_NAME })),
|
||||||
|
Role.findOneBy({ role_name: READER_NAME }).then(r => r || Role.save({ role_name: READER_NAME })),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Role initialization failed:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sample users
|
||||||
|
|
||||||
Role.save([admin_role, editor_role, reader_role]);
|
|
||||||
|
|
||||||
let admin_user: User;
|
let admin_user: User;
|
||||||
let editor_user: User;
|
let editor_user: User;
|
||||||
let reader_user: User;
|
let reader_user: User;
|
||||||
|
|
||||||
await User.findOne({where:
|
try {
|
||||||
{user_name:ADMIN_NAME}
|
[admin_user, editor_user, reader_user] = await Promise.all([
|
||||||
}).then(async (u)=>{
|
User.findOne({ where: { user_name: ADMIN_NAME } })
|
||||||
//if no admin user is detected, generate one with the admin role
|
.then(u =>
|
||||||
if (!u){
|
u || User.save(generate_user(ADMIN_NAME, ADMIN_NAME, [admin_role]))),
|
||||||
admin_user = new User();
|
User.findOne({ where: { user_name: EDITOR_NAME } })
|
||||||
admin_user.user_name = ADMIN_NAME;
|
.then(u =>
|
||||||
admin_user.roles = [admin_role];
|
u || User.save(generate_user(EDITOR_NAME, EDITOR_NAME, [editor_role]))),
|
||||||
await admin_user
|
User.findOne({ where: { user_name: READER_NAME } })
|
||||||
.save()
|
.then(u =>
|
||||||
.then((u)=>{
|
u || User.save(generate_user(READER_NAME, READER_NAME, [reader_role]))),
|
||||||
admin_user = u;
|
]);
|
||||||
});
|
} catch (error) {
|
||||||
}
|
console.error("User initialization failed:", error);
|
||||||
})
|
}
|
||||||
|
|
||||||
await User.findOne({where:
|
}
|
||||||
{user_name:EDITOR_NAME}
|
|
||||||
}).then(async (u)=>{
|
|
||||||
if (!u){
|
|
||||||
editor_user = new User();
|
|
||||||
editor_user.user_name = EDITOR_NAME;
|
|
||||||
editor_user.roles = [editor_role];
|
|
||||||
await editor_user
|
|
||||||
.save()
|
|
||||||
.then((u)=>{
|
|
||||||
editor_user = u;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await User.findOne({where:
|
|
||||||
{user_name:READER_NAME}
|
|
||||||
}).then(async (u)=>{
|
|
||||||
if (!u){
|
|
||||||
reader_user = new User();
|
|
||||||
reader_user.user_name = READER_NAME;
|
|
||||||
reader_user.roles = [reader_role];
|
|
||||||
await reader_user
|
|
||||||
.save()
|
|
||||||
.then((u)=>{
|
|
||||||
reader_user = u;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
function generate_user(user_name: string, password: string, roles:Role[]): User{
|
||||||
|
let user = new User();
|
||||||
|
user.user_name = user_name;
|
||||||
|
user.roles = roles;
|
||||||
|
user.setPassword(password);
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initExpress() {
|
function initExpress() {
|
||||||
|
|
@ -102,6 +88,29 @@ function initExpress() {
|
||||||
console.info(path.join(__dirname, '../../ktb-static'));
|
console.info(path.join(__dirname, '../../ktb-static'));
|
||||||
|
|
||||||
app.use(express.json(), express.static(path.join(__dirname, '../../ktb-static')));
|
app.use(express.json(), express.static(path.join(__dirname, '../../ktb-static')));
|
||||||
|
|
||||||
|
passport.use(new BasicStrategy(
|
||||||
|
async function(user_name, password, cb) {
|
||||||
|
const user = await User.findOneBy({user_name});
|
||||||
|
if(!user){
|
||||||
|
return cb(null, false);
|
||||||
|
}
|
||||||
|
if(!user.checkPassword(password)){
|
||||||
|
return cb(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(null, user);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
app.use(passport.initialize());
|
||||||
|
app.use(passport.session());
|
||||||
|
|
||||||
|
app.get("/login",
|
||||||
|
passport.authenticate('basic', { session: false }),
|
||||||
|
(_req, res) => {
|
||||||
|
res.status(200).send();
|
||||||
|
});
|
||||||
|
|
||||||
app.get("/sample", (_req, res) => {
|
app.get("/sample", (_req, res) => {
|
||||||
res.status(200).send(SAMPLE);
|
res.status(200).send(SAMPLE);
|
||||||
|
|
|
||||||
1149
pnpm-lock.yaml
generated
1149
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue