mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
commit
d894358426
5
backend/src/auth/CustomJwtPayload.ts
Normal file
5
backend/src/auth/CustomJwtPayload.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { JwtPayload } from 'jsonwebtoken'
|
||||||
|
|
||||||
|
export interface CustomJwtPayload extends JwtPayload {
|
||||||
|
pubKey: Buffer
|
||||||
|
}
|
||||||
13
backend/src/auth/INALIENABLE_RIGHTS.ts
Normal file
13
backend/src/auth/INALIENABLE_RIGHTS.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
|
||||||
|
export const INALIENABLE_RIGHTS = [
|
||||||
|
RIGHTS.LOGIN,
|
||||||
|
RIGHTS.GET_COMMUNITY_INFO,
|
||||||
|
RIGHTS.COMMUNITIES,
|
||||||
|
RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE,
|
||||||
|
RIGHTS.CREATE_USER,
|
||||||
|
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||||
|
RIGHTS.RESET_PASSWORD,
|
||||||
|
RIGHTS.CHECK_USERNAME,
|
||||||
|
RIGHTS.CHECK_EMAIL,
|
||||||
|
]
|
||||||
19
backend/src/auth/JWT.ts
Normal file
19
backend/src/auth/JWT.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import CONFIG from '../config/'
|
||||||
|
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||||
|
|
||||||
|
export const decode = (token: string): CustomJwtPayload | null => {
|
||||||
|
if (!token) throw new Error('401 Unauthorized')
|
||||||
|
try {
|
||||||
|
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const encode = (pubKey: Buffer): string => {
|
||||||
|
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
||||||
|
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||||
|
})
|
||||||
|
return token
|
||||||
|
}
|
||||||
26
backend/src/auth/RIGHTS.ts
Normal file
26
backend/src/auth/RIGHTS.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export enum RIGHTS {
|
||||||
|
LOGIN = 'LOGIN',
|
||||||
|
VERIFY_LOGIN = 'VERIFY_LOGIN',
|
||||||
|
BALANCE = 'BALANCE',
|
||||||
|
GET_COMMUNITY_INFO = 'GET_COMMUNITY_INFO',
|
||||||
|
COMMUNITIES = 'COMMUNITIES',
|
||||||
|
LIST_GDT_ENTRIES = 'LIST_GDT_ENTRIES',
|
||||||
|
EXIST_PID = 'EXIST_PID',
|
||||||
|
GET_KLICKTIPP_USER = 'GET_KLICKTIPP_USER',
|
||||||
|
GET_KLICKTIPP_TAG_MAP = 'GET_KLICKTIPP_TAG_MAP',
|
||||||
|
UNSUBSCRIBE_NEWSLETTER = 'UNSUBSCRIBE_NEWSLETTER',
|
||||||
|
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
||||||
|
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||||
|
SEND_COINS = 'SEND_COINS',
|
||||||
|
LOGIN_VIA_EMAIL_VERIFICATION_CODE = 'LOGIN_VIA_EMAIL_VERIFICATION_CODE',
|
||||||
|
LOGOUT = 'LOGOUT',
|
||||||
|
CREATE_USER = 'CREATE_USER',
|
||||||
|
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||||
|
RESET_PASSWORD = 'RESET_PASSWORD',
|
||||||
|
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||||
|
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||||
|
CHECK_EMAIL = 'CHECK_EMAIL',
|
||||||
|
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||||
|
// Admin
|
||||||
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
|
}
|
||||||
25
backend/src/auth/ROLES.ts
Normal file
25
backend/src/auth/ROLES.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS'
|
||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
import { Role } from './Role'
|
||||||
|
|
||||||
|
export const ROLE_UNAUTHORIZED = new Role('unauthorized', INALIENABLE_RIGHTS)
|
||||||
|
export const ROLE_USER = new Role('user', [
|
||||||
|
...INALIENABLE_RIGHTS,
|
||||||
|
RIGHTS.VERIFY_LOGIN,
|
||||||
|
RIGHTS.BALANCE,
|
||||||
|
RIGHTS.LIST_GDT_ENTRIES,
|
||||||
|
RIGHTS.EXIST_PID,
|
||||||
|
RIGHTS.GET_KLICKTIPP_USER,
|
||||||
|
RIGHTS.GET_KLICKTIPP_TAG_MAP,
|
||||||
|
RIGHTS.UNSUBSCRIBE_NEWSLETTER,
|
||||||
|
RIGHTS.SUBSCRIBE_NEWSLETTER,
|
||||||
|
RIGHTS.TRANSACTION_LIST,
|
||||||
|
RIGHTS.SEND_COINS,
|
||||||
|
RIGHTS.LOGOUT,
|
||||||
|
RIGHTS.UPDATE_USER_INFOS,
|
||||||
|
RIGHTS.HAS_ELOPAGE,
|
||||||
|
])
|
||||||
|
export const ROLE_ADMIN = new Role('admin', Object.values(RIGHTS)) // all rights
|
||||||
|
|
||||||
|
// TODO from database
|
||||||
|
export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN]
|
||||||
15
backend/src/auth/Role.ts
Normal file
15
backend/src/auth/Role.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { RIGHTS } from './RIGHTS'
|
||||||
|
|
||||||
|
export class Role {
|
||||||
|
id: string
|
||||||
|
rights: RIGHTS[]
|
||||||
|
|
||||||
|
constructor(id: string, rights: RIGHTS[]) {
|
||||||
|
this.id = id
|
||||||
|
this.rights = rights
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRight = (right: RIGHTS): boolean => {
|
||||||
|
return this.rights.includes(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,9 @@ export default class CreateUserArgs {
|
|||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
lastName: string
|
lastName: string
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
password: string
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
language?: string // Will default to DEFAULT_LANGUAGE
|
language?: string // Will default to DEFAULT_LANGUAGE
|
||||||
|
|
||||||
|
|||||||
@ -2,19 +2,44 @@
|
|||||||
|
|
||||||
import { AuthChecker } from 'type-graphql'
|
import { AuthChecker } from 'type-graphql'
|
||||||
|
|
||||||
import decode from '../../jwt/decode'
|
import { decode, encode } from '../../auth/JWT'
|
||||||
import encode from '../../jwt/encode'
|
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '../../auth/ROLES'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||||
|
import { getCustomRepository } from 'typeorm'
|
||||||
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
|
|
||||||
const isAuthorized: AuthChecker<any> = async (
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
{ /* root, args, */ context /*, info */ } /*, roles */,
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
) => {
|
|
||||||
|
// Do we have a token?
|
||||||
if (context.token) {
|
if (context.token) {
|
||||||
const decoded = decode(context.token)
|
const decoded = decode(context.token)
|
||||||
|
if (!decoded) {
|
||||||
|
// we always throw on an invalid token
|
||||||
|
throw new Error('403.13 - Client certificate revoked')
|
||||||
|
}
|
||||||
|
// Set context pubKey
|
||||||
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
||||||
|
// set new header token
|
||||||
|
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
||||||
|
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
||||||
|
const userRepository = await getCustomRepository(UserRepository)
|
||||||
|
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
||||||
|
const serverUserRepository = await getCustomRepository(ServerUserRepository)
|
||||||
|
const countServerUsers = await serverUserRepository.count({ email: user.email })
|
||||||
|
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
|
||||||
|
|
||||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
throw new Error('401 Unauthorized')
|
|
||||||
|
// check for correct rights
|
||||||
|
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
||||||
|
if (missingRights.length !== 0) {
|
||||||
|
throw new Error('401 Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export default isAuthorized
|
export default isAuthorized
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Resolver, Query, Arg } from 'type-graphql'
|
import { Resolver, Query, Arg, Authorized } from 'type-graphql'
|
||||||
import { getCustomRepository } from 'typeorm'
|
import { getCustomRepository } from 'typeorm'
|
||||||
import { UserAdmin } from '../model/UserAdmin'
|
import { UserAdmin } from '../model/UserAdmin'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class AdminResolver {
|
export class AdminResolver {
|
||||||
|
@Authorized([RIGHTS.SEARCH_USERS])
|
||||||
@Query(() => [UserAdmin])
|
@Query(() => [UserAdmin])
|
||||||
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
async searchUsers(@Arg('searchText') searchText: string): Promise<UserAdmin[]> {
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
|
|||||||
@ -8,10 +8,11 @@ import { BalanceRepository } from '../../typeorm/repository/Balance'
|
|||||||
import { UserRepository } from '../../typeorm/repository/User'
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
import { calculateDecay } from '../../util/decay'
|
import { calculateDecay } from '../../util/decay'
|
||||||
import { roundFloorFrom4 } from '../../util/round'
|
import { roundFloorFrom4 } from '../../util/round'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class BalanceResolver {
|
export class BalanceResolver {
|
||||||
@Authorized()
|
@Authorized([RIGHTS.BALANCE])
|
||||||
@Query(() => Balance)
|
@Query(() => Balance)
|
||||||
async balance(@Ctx() context: any): Promise<Balance> {
|
async balance(@Ctx() context: any): Promise<Balance> {
|
||||||
// load user and balance
|
// load user and balance
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|
||||||
import { Resolver, Query } from 'type-graphql'
|
import { Resolver, Query, Authorized } from 'type-graphql'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { Community } from '../model/Community'
|
import { Community } from '../model/Community'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class CommunityResolver {
|
export class CommunityResolver {
|
||||||
|
@Authorized([RIGHTS.GET_COMMUNITY_INFO])
|
||||||
@Query(() => Community)
|
@Query(() => Community)
|
||||||
async getCommunityInfo(): Promise<Community> {
|
async getCommunityInfo(): Promise<Community> {
|
||||||
return new Community({
|
return new Community({
|
||||||
@ -17,6 +19,7 @@ export class CommunityResolver {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.COMMUNITIES])
|
||||||
@Query(() => [Community])
|
@Query(() => [Community])
|
||||||
async communities(): Promise<Community[]> {
|
async communities(): Promise<Community[]> {
|
||||||
if (CONFIG.PRODUCTION)
|
if (CONFIG.PRODUCTION)
|
||||||
|
|||||||
@ -9,10 +9,11 @@ import Paginated from '../arg/Paginated'
|
|||||||
import { apiGet } from '../../apis/HttpRequest'
|
import { apiGet } from '../../apis/HttpRequest'
|
||||||
import { UserRepository } from '../../typeorm/repository/User'
|
import { UserRepository } from '../../typeorm/repository/User'
|
||||||
import { Order } from '../enum/Order'
|
import { Order } from '../enum/Order'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class GdtResolver {
|
export class GdtResolver {
|
||||||
@Authorized()
|
@Authorized([RIGHTS.LIST_GDT_ENTRIES])
|
||||||
@Query(() => GdtEntryList)
|
@Query(() => GdtEntryList)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async listGDTEntries(
|
async listGDTEntries(
|
||||||
@ -33,7 +34,7 @@ export class GdtResolver {
|
|||||||
return new GdtEntryList(resultGDT.data)
|
return new GdtEntryList(resultGDT.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.EXIST_PID])
|
||||||
@Query(() => Number)
|
@Query(() => Number)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async existPid(@Arg('pid') pid: number): Promise<number> {
|
async existPid(@Arg('pid') pid: number): Promise<number> {
|
||||||
|
|||||||
@ -8,29 +8,30 @@ import {
|
|||||||
unsubscribe,
|
unsubscribe,
|
||||||
signIn,
|
signIn,
|
||||||
} from '../../apis/KlicktippController'
|
} from '../../apis/KlicktippController'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
|
import SubscribeNewsletterArgs from '../arg/SubscribeNewsletterArgs'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class KlicktippResolver {
|
export class KlicktippResolver {
|
||||||
@Authorized()
|
@Authorized([RIGHTS.GET_KLICKTIPP_USER])
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async getKlicktippUser(@Arg('email') email: string): Promise<string> {
|
async getKlicktippUser(@Arg('email') email: string): Promise<string> {
|
||||||
return await getKlickTippUser(email)
|
return await getKlickTippUser(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.GET_KLICKTIPP_TAG_MAP])
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async getKlicktippTagMap(): Promise<string> {
|
async getKlicktippTagMap(): Promise<string> {
|
||||||
return await getKlicktippTagMap()
|
return await getKlicktippTagMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.UNSUBSCRIBE_NEWSLETTER])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
|
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
|
||||||
return await unsubscribe(email)
|
return await unsubscribe(email)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.SUBSCRIBE_NEWSLETTER])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async subscribeNewsletter(
|
async subscribeNewsletter(
|
||||||
@Args() { email, language }: SubscribeNewsletterArgs,
|
@Args() { email, language }: SubscribeNewsletterArgs,
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import { TransactionTypeId } from '../enum/TransactionTypeId'
|
|||||||
import { TransactionType } from '../enum/TransactionType'
|
import { TransactionType } from '../enum/TransactionType'
|
||||||
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
# Test
|
# Test
|
||||||
@ -465,7 +466,7 @@ async function getPublicKey(email: string): Promise<string | null> {
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class TransactionResolver {
|
export class TransactionResolver {
|
||||||
@Authorized()
|
@Authorized([RIGHTS.TRANSACTION_LIST])
|
||||||
@Query(() => TransactionList)
|
@Query(() => TransactionList)
|
||||||
async transactionList(
|
async transactionList(
|
||||||
@Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
@Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated,
|
||||||
@ -499,7 +500,7 @@ export class TransactionResolver {
|
|||||||
return transactions
|
return transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.SEND_COINS])
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async sendCoins(
|
async sendCoins(
|
||||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
|||||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
||||||
import { User } from '../model/User'
|
import { User } from '../model/User'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
import encode from '../../jwt/encode'
|
import { encode } from '../../auth/JWT'
|
||||||
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
||||||
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
||||||
import CreateUserArgs from '../arg/CreateUserArgs'
|
import CreateUserArgs from '../arg/CreateUserArgs'
|
||||||
@ -30,6 +30,9 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
|
|||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||||
import { sendEMail } from '../../util/sendEMail'
|
import { sendEMail } from '../../util/sendEMail'
|
||||||
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
||||||
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||||
|
import { ROLE_ADMIN } from '../../auth/ROLES'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const sodium = require('sodium-native')
|
const sodium = require('sodium-native')
|
||||||
@ -194,37 +197,7 @@ const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: B
|
|||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
/*
|
@Authorized([RIGHTS.VERIFY_LOGIN])
|
||||||
@Authorized()
|
|
||||||
@Query(() => User)
|
|
||||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
|
||||||
loginUser = loginUserRepository.findByPubkeyHex()
|
|
||||||
const user = new User(result.data.user)
|
|
||||||
|
|
||||||
this.email = json.email
|
|
||||||
this.firstName = json.first_name
|
|
||||||
this.lastName = json.last_name
|
|
||||||
this.username = json.username
|
|
||||||
this.description = json.description
|
|
||||||
this.pubkey = json.public_hex
|
|
||||||
this.language = json.language
|
|
||||||
this.publisherId = json.publisher_id
|
|
||||||
this.isAdmin = json.isAdmin
|
|
||||||
|
|
||||||
const userSettingRepository = getCustomRepository(UserSettingRepository)
|
|
||||||
const coinanimation = await userSettingRepository
|
|
||||||
.readBoolean(userEntity.id, Setting.COIN_ANIMATION)
|
|
||||||
.catch((error) => {
|
|
||||||
throw new Error(error)
|
|
||||||
})
|
|
||||||
user.coinanimation = coinanimation
|
|
||||||
user.isAdmin = true // TODO implement
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Authorized()
|
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||||
async verifyLogin(@Ctx() context: any): Promise<User> {
|
async verifyLogin(@Ctx() context: any): Promise<User> {
|
||||||
@ -253,10 +226,12 @@ export class UserResolver {
|
|||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
user.isAdmin = true // TODO implement
|
|
||||||
|
user.isAdmin = context.role === ROLE_ADMIN
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.LOGIN])
|
||||||
@Query(() => User)
|
@Query(() => User)
|
||||||
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
@UseMiddleware(klicktippNewsletterStateMiddleware)
|
||||||
async login(
|
async login(
|
||||||
@ -329,7 +304,11 @@ export class UserResolver {
|
|||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
})
|
})
|
||||||
user.coinanimation = coinanimation
|
user.coinanimation = coinanimation
|
||||||
user.isAdmin = true // TODO implement
|
|
||||||
|
// context.role is not set to the actual role yet on login
|
||||||
|
const serverUserRepository = await getCustomRepository(ServerUserRepository)
|
||||||
|
const countServerUsers = await serverUserRepository.count({ email: user.email })
|
||||||
|
user.isAdmin = countServerUsers > 0
|
||||||
|
|
||||||
context.setHeaders.push({
|
context.setHeaders.push({
|
||||||
key: 'token',
|
key: 'token',
|
||||||
@ -339,6 +318,7 @@ export class UserResolver {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE])
|
||||||
@Query(() => LoginViaVerificationCode)
|
@Query(() => LoginViaVerificationCode)
|
||||||
async loginViaEmailVerificationCode(
|
async loginViaEmailVerificationCode(
|
||||||
@Arg('optin') optin: string,
|
@Arg('optin') optin: string,
|
||||||
@ -354,7 +334,7 @@ export class UserResolver {
|
|||||||
return new LoginViaVerificationCode(result.data)
|
return new LoginViaVerificationCode(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.LOGOUT])
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async logout(): Promise<boolean> {
|
async logout(): Promise<boolean> {
|
||||||
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
// TODO: We dont need this anymore, but might need this in the future in oder to invalidate a valid JWT-Token.
|
||||||
@ -365,9 +345,10 @@ export class UserResolver {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.CREATE_USER])
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async createUser(
|
async createUser(
|
||||||
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||||
// default int publisher_id = 0;
|
// default int publisher_id = 0;
|
||||||
@ -377,13 +358,12 @@ export class UserResolver {
|
|||||||
language = DEFAULT_LANGUAGE
|
language = DEFAULT_LANGUAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Register process
|
|
||||||
// Validate Password
|
// Validate Password
|
||||||
// if (!isPassword(password)) {
|
if (!isPassword(password)) {
|
||||||
// throw new Error(
|
throw new Error(
|
||||||
// 'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Validate username
|
// Validate username
|
||||||
// TODO: never true
|
// TODO: never true
|
||||||
@ -401,13 +381,11 @@ export class UserResolver {
|
|||||||
throw new Error(`User already exists.`)
|
throw new Error(`User already exists.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Register process
|
const passphrase = PassphraseGenerate()
|
||||||
// const passphrase = PassphraseGenerate()
|
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
|
||||||
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
|
|
||||||
const emailHash = getEmailHash(email)
|
const emailHash = getEmailHash(email)
|
||||||
|
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
|
|
||||||
// Table: login_users
|
// Table: login_users
|
||||||
const loginUser = new LoginUser()
|
const loginUser = new LoginUser()
|
||||||
@ -416,15 +394,13 @@ export class UserResolver {
|
|||||||
loginUser.lastName = lastName
|
loginUser.lastName = lastName
|
||||||
loginUser.username = username
|
loginUser.username = username
|
||||||
loginUser.description = ''
|
loginUser.description = ''
|
||||||
// TODO: Register process
|
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
|
||||||
loginUser.emailHash = emailHash
|
loginUser.emailHash = emailHash
|
||||||
loginUser.language = language
|
loginUser.language = language
|
||||||
loginUser.groupId = 1
|
loginUser.groupId = 1
|
||||||
loginUser.publisherId = publisherId
|
loginUser.publisherId = publisherId
|
||||||
// TODO: Register process
|
loginUser.pubKey = keyPair[0]
|
||||||
// loginUser.pubKey = keyPair[0]
|
loginUser.privKey = encryptedPrivkey
|
||||||
// loginUser.privKey = encryptedPrivkey
|
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -436,24 +412,21 @@ export class UserResolver {
|
|||||||
throw new Error('insert user failed')
|
throw new Error('insert user failed')
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Register process
|
|
||||||
// Table: login_user_backups
|
// Table: login_user_backups
|
||||||
// const loginUserBackup = new LoginUserBackup()
|
const loginUserBackup = new LoginUserBackup()
|
||||||
// loginUserBackup.userId = loginUserId
|
loginUserBackup.userId = loginUserId
|
||||||
// loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
loginUserBackup.passphrase = passphrase.join(' ') + ' ' // login server saves trailing space
|
||||||
// loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
loginUserBackup.mnemonicType = 2 // ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
|
||||||
|
|
||||||
// TODO: Register process
|
await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
||||||
// await queryRunner.manager.save(loginUserBackup).catch((error) => {
|
// eslint-disable-next-line no-console
|
||||||
// // eslint-disable-next-line no-console
|
console.log('insert LoginUserBackup failed', error)
|
||||||
// console.log('insert LoginUserBackup failed', error)
|
throw new Error('insert user backup failed')
|
||||||
// throw new Error('insert user backup failed')
|
})
|
||||||
// })
|
|
||||||
|
|
||||||
// Table: state_users
|
// Table: state_users
|
||||||
const dbUser = new DbUser()
|
const dbUser = new DbUser()
|
||||||
// TODO: Register process
|
dbUser.pubkey = keyPair[0]
|
||||||
// dbUser.pubkey = keyPair[0]
|
|
||||||
dbUser.email = email
|
dbUser.email = email
|
||||||
dbUser.firstName = firstName
|
dbUser.firstName = firstName
|
||||||
dbUser.lastName = lastName
|
dbUser.lastName = lastName
|
||||||
@ -513,6 +486,7 @@ export class UserResolver {
|
|||||||
return 'success'
|
return 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||||
@Query(() => SendPasswordResetEmailResponse)
|
@Query(() => SendPasswordResetEmailResponse)
|
||||||
async sendResetPasswordEmail(
|
async sendResetPasswordEmail(
|
||||||
@Arg('email') email: string,
|
@Arg('email') email: string,
|
||||||
@ -529,6 +503,7 @@ export class UserResolver {
|
|||||||
return new SendPasswordResetEmailResponse(response.data)
|
return new SendPasswordResetEmailResponse(response.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.RESET_PASSWORD])
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async resetPassword(
|
async resetPassword(
|
||||||
@Args()
|
@Args()
|
||||||
@ -546,7 +521,7 @@ export class UserResolver {
|
|||||||
return 'success'
|
return 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async updateUserInfos(
|
async updateUserInfos(
|
||||||
@Args()
|
@Args()
|
||||||
@ -655,6 +630,7 @@ export class UserResolver {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.CHECK_USERNAME])
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async checkUsername(@Args() { username }: CheckUsernameArgs): Promise<boolean> {
|
async checkUsername(@Args() { username }: CheckUsernameArgs): Promise<boolean> {
|
||||||
// Username empty?
|
// Username empty?
|
||||||
@ -678,6 +654,7 @@ export class UserResolver {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.CHECK_EMAIL])
|
||||||
@Query(() => CheckEmailResponse)
|
@Query(() => CheckEmailResponse)
|
||||||
@UseMiddleware(klicktippRegistrationMiddleware)
|
@UseMiddleware(klicktippRegistrationMiddleware)
|
||||||
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
|
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
|
||||||
@ -690,7 +667,7 @@ export class UserResolver {
|
|||||||
return new CheckEmailResponse(result.data)
|
return new CheckEmailResponse(result.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized()
|
@Authorized([RIGHTS.HAS_ELOPAGE])
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
import jwt, { JwtPayload } from 'jsonwebtoken'
|
|
||||||
import CONFIG from '../config/'
|
|
||||||
|
|
||||||
interface CustomJwtPayload extends JwtPayload {
|
|
||||||
pubKey: Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type DecodedJwt = {
|
|
||||||
token: string
|
|
||||||
pubKey: Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (token: string): DecodedJwt => {
|
|
||||||
if (!token) throw new Error('401 Unauthorized')
|
|
||||||
let pubKey = null
|
|
||||||
try {
|
|
||||||
const decoded = <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
|
||||||
pubKey = decoded.pubKey
|
|
||||||
return {
|
|
||||||
token,
|
|
||||||
pubKey,
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('403.13 - Client certificate revoked')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
|
|
||||||
import jwt from 'jsonwebtoken'
|
|
||||||
import CONFIG from '../config/'
|
|
||||||
|
|
||||||
// Generate an Access Token
|
|
||||||
export default function encode(pubKey: Buffer): string {
|
|
||||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
|
||||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
|
||||||
})
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
5
backend/src/typeorm/repository/ServerUser.ts
Normal file
5
backend/src/typeorm/repository/ServerUser.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { EntityRepository, Repository } from 'typeorm'
|
||||||
|
import { ServerUser } from '@entity/ServerUser'
|
||||||
|
|
||||||
|
@EntityRepository(ServerUser)
|
||||||
|
export class ServerUserRepository extends Repository<ServerUser> {}
|
||||||
@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
import { LoginElopageBuys } from '@entity/LoginElopageBuys'
|
||||||
import { LoginUser } from '@entity/LoginUser'
|
import { LoginUser } from '@entity/LoginUser'
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
import { UserResolver } from '../graphql/resolver/UserResolver'
|
import { UserResolver } from '../graphql/resolver/UserResolver'
|
||||||
|
|
||||||
export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||||
@ -145,6 +144,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
publisherId: loginElopgaeBuy.publisherId,
|
publisherId: loginElopgaeBuy.publisherId,
|
||||||
|
password: '123', // TODO remove
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|||||||
31
database/entity/0001-init_db/ServerUser.ts
Normal file
31
database/entity/0001-init_db/ServerUser.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('server_users')
|
||||||
|
export class ServerUser extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ length: 50 })
|
||||||
|
username: string
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', unsigned: true })
|
||||||
|
password: BigInt
|
||||||
|
|
||||||
|
@Column({ length: 50, unique: true })
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Column({ length: 20, default: 'admin' })
|
||||||
|
role: string
|
||||||
|
|
||||||
|
@Column({ default: 0 })
|
||||||
|
activated: number
|
||||||
|
|
||||||
|
@Column({ name: 'last_login', default: null, nullable: true })
|
||||||
|
lastLogin: Date
|
||||||
|
|
||||||
|
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
created: Date
|
||||||
|
|
||||||
|
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
|
modified: Date
|
||||||
|
}
|
||||||
@ -1,31 +1 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
export { ServerUser } from './0001-init_db/ServerUser'
|
||||||
|
|
||||||
@Entity('server_users')
|
|
||||||
export class ServerUser extends BaseEntity {
|
|
||||||
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
|
||||||
id: number
|
|
||||||
|
|
||||||
@Column({ length: 50 })
|
|
||||||
username: string
|
|
||||||
|
|
||||||
@Column({ type: 'bigint', unsigned: true })
|
|
||||||
password: BigInt
|
|
||||||
|
|
||||||
@Column({ length: 50, unique: true })
|
|
||||||
email: string
|
|
||||||
|
|
||||||
@Column({ length: 20, default: 'admin' })
|
|
||||||
role: string
|
|
||||||
|
|
||||||
@Column({ default: 0 })
|
|
||||||
activated: number
|
|
||||||
|
|
||||||
@Column({ name: 'last_login', default: null, nullable: true })
|
|
||||||
lastLogin: Date
|
|
||||||
|
|
||||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
|
||||||
created: Date
|
|
||||||
|
|
||||||
@Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP' })
|
|
||||||
modified: Date
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { LoginEmailOptIn } from './LoginEmailOptIn'
|
|||||||
import { LoginUser } from './LoginUser'
|
import { LoginUser } from './LoginUser'
|
||||||
import { LoginUserBackup } from './LoginUserBackup'
|
import { LoginUserBackup } from './LoginUserBackup'
|
||||||
import { Migration } from './Migration'
|
import { Migration } from './Migration'
|
||||||
|
import { ServerUser } from './ServerUser'
|
||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
import { TransactionCreation } from './TransactionCreation'
|
import { TransactionCreation } from './TransactionCreation'
|
||||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||||
@ -18,6 +19,7 @@ export const entities = [
|
|||||||
LoginUser,
|
LoginUser,
|
||||||
LoginUserBackup,
|
LoginUserBackup,
|
||||||
Migration,
|
Migration,
|
||||||
|
ServerUser,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionCreation,
|
TransactionCreation,
|
||||||
TransactionSendCoin,
|
TransactionSendCoin,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const addNavigationGuards = (router, store, apollo) => {
|
|||||||
// store token on authenticate
|
// store token on authenticate
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
if (to.path === '/authenticate' && to.query.token) {
|
if (to.path === '/authenticate' && to.query.token) {
|
||||||
// TODO verify user in order to get user data
|
|
||||||
store.commit('token', to.query.token)
|
store.commit('token', to.query.token)
|
||||||
const result = await apollo.query({
|
const result = await apollo.query({
|
||||||
query: verifyLogin,
|
query: verifyLogin,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user