feat: refactor deleting account (#676)

* fix(api-gateway): TYPES aliases

* feat: refactor account deleting
This commit is contained in:
Karol Sójko
2023-07-31 13:23:39 +02:00
committed by GitHub
parent d2b0fb144b
commit 0d5dcdd8ec
70 changed files with 684 additions and 342 deletions

View File

@@ -46,7 +46,7 @@ void container.load().then((container) => {
server.setConfig((app) => {
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-API-Gateway-Version', container.get(TYPES.VERSION))
response.setHeader('X-API-Gateway-Version', container.get(TYPES.ApiGateway_VERSION))
next()
})
app.use(
@@ -87,7 +87,7 @@ void container.load().then((container) => {
)
})
const logger: winston.Logger = container.get(TYPES.Logger)
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {

View File

@@ -21,6 +21,7 @@
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts",
"setup:env": "cp .env.sample .env",
"start": "yarn node dist/bin/server.js",
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"

View File

@@ -57,7 +57,7 @@ export class ContainerConfigLoader {
defaultMeta: { service: 'api-gateway' },
})
}
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
container.bind<winston.Logger>(TYPES.ApiGateway_Logger).toConstantValue(logger)
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
@@ -68,36 +68,39 @@ export class ContainerConfigLoader {
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Redis).toConstantValue(redis)
container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis)
}
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(axios.create())
// env vars
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL', true))
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
container.bind(TYPES.EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))
container.bind(TYPES.ApiGateway_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL', true))
container.bind(TYPES.ApiGateway_REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
container.bind(TYPES.ApiGateway_EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
container.bind(TYPES.ApiGateway_PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.ApiGateway_FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.ApiGateway_WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
container.bind(TYPES.ApiGateway_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container
.bind(TYPES.HTTP_CALL_TIMEOUT)
.bind(TYPES.ApiGateway_HTTP_CALL_TIMEOUT)
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
container.bind(TYPES.ApiGateway_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container
.bind(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL)
.toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
container.bind(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER).toConstantValue(isConfiguredForHomeServer)
// Middleware
container
.bind<RequiredCrossServiceTokenMiddleware>(TYPES.RequiredCrossServiceTokenMiddleware)
.bind<RequiredCrossServiceTokenMiddleware>(TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
.to(RequiredCrossServiceTokenMiddleware)
container
.bind<OptionalCrossServiceTokenMiddleware>(TYPES.OptionalCrossServiceTokenMiddleware)
.bind<OptionalCrossServiceTokenMiddleware>(TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
.to(OptionalCrossServiceTokenMiddleware)
container.bind<WebSocketAuthMiddleware>(TYPES.WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
container.bind<WebSocketAuthMiddleware>(TYPES.ApiGateway_WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
container
.bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)
.bind<SubscriptionTokenAuthMiddleware>(TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
.to(SubscriptionTokenAuthMiddleware)
// Services
@@ -106,24 +109,26 @@ export class ContainerConfigLoader {
throw new Error('Service container is required when configured for home server')
}
container
.bind<ServiceProxyInterface>(TYPES.ServiceProxy)
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.FILES_SERVER_URL)),
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
container.bind<ServiceProxyInterface>(TYPES.ServiceProxy).to(HttpServiceProxy)
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
}
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) {
container
.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache)
.toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.Timer)))
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.toConstantValue(new InMemoryCrossServiceTokenCache(container.get(TYPES.ApiGateway_Timer)))
} else {
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
.to(RedisCrossServiceTokenCache)
}
container
.bind<EndpointResolverInterface>(TYPES.EndpointResolver)
.bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
.toConstantValue(new EndpointResolver(isConfiguredForHomeServer))
logger.debug('Configuration complete')

View File

@@ -1,29 +1,28 @@
export const TYPES = {
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
HTTPClient: Symbol.for('HTTPClient'),
ApiGateway_Logger: Symbol.for('ApiGateway_Logger'),
ApiGateway_Redis: Symbol.for('ApiGateway_Redis'),
ApiGateway_HTTPClient: Symbol.for('ApiGateway_HTTPClient'),
// env vars
SYNCING_SERVER_JS_URL: Symbol.for('SYNCING_SERVER_JS_URL'),
AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
VERSION: Symbol.for('VERSION'),
CROSS_SERVICE_TOKEN_CACHE_TTL: Symbol.for('CROSS_SERVICE_TOKEN_CACHE_TTL'),
ApiGateway_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'),
ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'),
ApiGateway_PAYMENTS_SERVER_URL: Symbol.for('ApiGateway_PAYMENTS_SERVER_URL'),
ApiGateway_FILES_SERVER_URL: Symbol.for('ApiGateway_FILES_SERVER_URL'),
ApiGateway_REVISIONS_SERVER_URL: Symbol.for('ApiGateway_REVISIONS_SERVER_URL'),
ApiGateway_EMAIL_SERVER_URL: Symbol.for('ApiGateway_EMAIL_SERVER_URL'),
ApiGateway_WEB_SOCKET_SERVER_URL: Symbol.for('ApiGateway_WEB_SOCKET_SERVER_URL'),
ApiGateway_AUTH_JWT_SECRET: Symbol.for('ApiGateway_AUTH_JWT_SECRET'),
ApiGateway_HTTP_CALL_TIMEOUT: Symbol.for('ApiGateway_HTTP_CALL_TIMEOUT'),
ApiGateway_VERSION: Symbol.for('ApiGateway_VERSION'),
ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL: Symbol.for('ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL'),
ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER: Symbol.for('ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER'),
// Middleware
RequiredCrossServiceTokenMiddleware: Symbol.for('RequiredCrossServiceTokenMiddleware'),
OptionalCrossServiceTokenMiddleware: Symbol.for('OptionalCrossServiceTokenMiddleware'),
WebSocketAuthMiddleware: Symbol.for('WebSocketAuthMiddleware'),
SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
ApiGateway_RequiredCrossServiceTokenMiddleware: Symbol.for('ApiGateway_RequiredCrossServiceTokenMiddleware'),
ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'),
ApiGateway_WebSocketAuthMiddleware: Symbol.for('ApiGateway_WebSocketAuthMiddleware'),
ApiGateway_SubscriptionTokenAuthMiddleware: Symbol.for('ApiGateway_SubscriptionTokenAuthMiddleware'),
// Services
ServiceProxy: Symbol.for('ServiceProxy'),
CrossServiceTokenCache: Symbol.for('CrossServiceTokenCache'),
Timer: Symbol.for('Timer'),
EndpointResolver: Symbol.for('EndpointResolver'),
ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'),
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),
ApiGateway_Timer: Symbol.for('ApiGateway_Timer'),
ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'),
}
// export default TYPES

View File

@@ -9,7 +9,7 @@ export class LegacyController extends BaseHttpController {
private AUTH_ROUTES: Map<string, string>
private PARAMETRIZED_AUTH_ROUTES: Map<string, string>
constructor(@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface) {
constructor(@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface) {
super()
this.AUTH_ROUTES = new Map([
@@ -29,17 +29,17 @@ export class LegacyController extends BaseHttpController {
])
}
@httpPost('/items/sync', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/items/sync', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async legacyItemsSync(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/items/:item_id/revisions', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async legacyGetRevisions(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions/:id', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/items/:item_id/revisions/:id', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async legacyGetRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}

View File

@@ -11,12 +11,12 @@ import { AuthMiddleware } from './AuthMiddleware'
@injectable()
export class OptionalCrossServiceTokenMiddleware extends AuthMiddleware {
constructor(
@inject(TYPES.ServiceProxy) serviceProxy: ServiceProxyInterface,
@inject(TYPES.AUTH_JWT_SECRET) jwtSecret: string,
@inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
@inject(TYPES.CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Timer) timer: TimerInterface,
@inject(TYPES.Logger) logger: Logger,
@inject(TYPES.ApiGateway_ServiceProxy) serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_AUTH_JWT_SECRET) jwtSecret: string,
@inject(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
@inject(TYPES.ApiGateway_CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.ApiGateway_Timer) timer: TimerInterface,
@inject(TYPES.ApiGateway_Logger) logger: Logger,
) {
super(serviceProxy, jwtSecret, crossServiceTokenCacheTTL, crossServiceTokenCache, timer, logger)
}

View File

@@ -11,12 +11,12 @@ import { AuthMiddleware } from './AuthMiddleware'
@injectable()
export class RequiredCrossServiceTokenMiddleware extends AuthMiddleware {
constructor(
@inject(TYPES.ServiceProxy) serviceProxy: ServiceProxyInterface,
@inject(TYPES.AUTH_JWT_SECRET) jwtSecret: string,
@inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
@inject(TYPES.CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Timer) timer: TimerInterface,
@inject(TYPES.Logger) logger: Logger,
@inject(TYPES.ApiGateway_ServiceProxy) serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_AUTH_JWT_SECRET) jwtSecret: string,
@inject(TYPES.ApiGateway_CROSS_SERVICE_TOKEN_CACHE_TTL) crossServiceTokenCacheTTL: number,
@inject(TYPES.ApiGateway_CrossServiceTokenCache) crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.ApiGateway_Timer) timer: TimerInterface,
@inject(TYPES.ApiGateway_Logger) logger: Logger,
) {
super(serviceProxy, jwtSecret, crossServiceTokenCacheTTL, crossServiceTokenCache, timer, logger)
}

View File

@@ -11,10 +11,10 @@ import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
@injectable()
export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.Logger) private logger: Logger,
@inject(TYPES.ApiGateway_HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.ApiGateway_AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.ApiGateway_AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
) {
super()
}

View File

@@ -12,10 +12,10 @@ import { TYPES } from '../Bootstrap/Types'
@injectable()
export class WebSocketAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.Logger) private logger: Logger,
@inject(TYPES.ApiGateway_HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.ApiGateway_AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.ApiGateway_AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
) {
super()
}

View File

@@ -8,8 +8,8 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1')
export class ActionsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@@ -24,7 +24,7 @@ export class ActionsController extends BaseHttpController {
)
}
@httpGet('/login-params', TYPES.OptionalCrossServiceTokenMiddleware)
@httpGet('/login-params', TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
async loginParams(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callAuthServer(
request,
@@ -34,7 +34,7 @@ export class ActionsController extends BaseHttpController {
)
}
@httpPost('/logout', TYPES.OptionalCrossServiceTokenMiddleware)
@httpPost('/logout', TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
async logout(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callAuthServer(
request,
@@ -54,7 +54,7 @@ export class ActionsController extends BaseHttpController {
)
}
@httpPost('/recovery/codes', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/recovery/codes', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async recoveryCodes(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callAuthServer(
request,

View File

@@ -9,13 +9,13 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/authenticators')
export class AuthenticatorsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpDelete('/:authenticatorId', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/:authenticatorId', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async delete(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -29,7 +29,7 @@ export class AuthenticatorsController extends BaseHttpController {
)
}
@httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async list(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -39,7 +39,7 @@ export class AuthenticatorsController extends BaseHttpController {
)
}
@httpGet('/generate-registration-options', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/generate-registration-options', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async generateRegistrationOptions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -59,7 +59,7 @@ export class AuthenticatorsController extends BaseHttpController {
)
}
@httpPost('/verify-registration', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/verify-registration', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async verifyRegistration(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -9,13 +9,13 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/files')
export class FilesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpPost('/valet-tokens', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/valet-tokens', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -6,11 +6,11 @@ import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
@controller('/v1')
export class InvoicesController extends BaseHttpController {
constructor(@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface) {
constructor(@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface) {
super()
}
@httpPost('/invoices/send-latest', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/invoices/send-latest', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async sendLatestInvoice(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-invoice', request.body)
}

View File

@@ -5,11 +5,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/items', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/items', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class ItemsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -5,11 +5,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/messages', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/messages', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class MessagesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -9,8 +9,8 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/offline')
export class OfflineController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -6,7 +6,7 @@ import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
@controller('/v1')
export class PaymentsController extends BaseHttpController {
constructor(@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface) {
constructor(@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface) {
super()
}
@@ -40,12 +40,12 @@ export class PaymentsController extends BaseHttpController {
await this.httpService.callPaymentsServer(request, response, 'api/extensions', request.body)
}
@httpPost('/subscriptions/tiered', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/subscriptions/tiered', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async createTieredSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/tiered', request.body)
}
@httpPost('/subscriptions/apple_iap_confirm', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/subscriptions/apple_iap_confirm', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async appleIAPConfirm(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/apple_iap_confirm', request.body)
}
@@ -140,7 +140,7 @@ export class PaymentsController extends BaseHttpController {
)
}
@httpPost('/payments/stripe-setup-intent', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/payments/stripe-setup-intent', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async createStripeSetupIntent(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/stripe-setup-intent', request.body)
}

View File

@@ -1,7 +1,7 @@
import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
@controller('/v1/items/:item_id/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/items/:item_id/revisions', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class RevisionsController extends BaseHttpController {
@httpGet('/')
async getRevisions(): Promise<results.JsonResult> {

View File

@@ -8,13 +8,13 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/sessions')
export class SessionsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -23,7 +23,7 @@ export class SessionsController extends BaseHttpController {
)
}
@httpDelete('/:uuid', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/:uuid', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async deleteSession(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -35,7 +35,7 @@ export class SessionsController extends BaseHttpController {
)
}
@httpDelete('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async deleteSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -5,11 +5,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class SharedVaultInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -5,11 +5,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class SharedVaultUsersController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -5,11 +5,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class SharedVaultsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -9,13 +9,13 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/subscription-invites')
export class SubscriptionInvitesController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpPost('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -25,7 +25,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
)
}
@httpGet('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async listInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -35,7 +35,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
)
}
@httpDelete('/:inviteUuid', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/:inviteUuid', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async cancelSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -48,7 +48,7 @@ export class SubscriptionInvitesController extends BaseHttpController {
)
}
@httpPost('/:inviteUuid/accept', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/:inviteUuid/accept', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async acceptInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -9,13 +9,13 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/subscription-tokens')
export class TokensController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@httpPost('/', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -20,9 +20,10 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/users')
export class UsersController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.Logger) private logger: Logger,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
@inject(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER) private isConfiguredForHomeServer: boolean,
) {
super()
}
@@ -32,12 +33,12 @@ export class UsersController extends BaseHttpController {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/claim-account', request.body)
}
@httpPost('/send-activation-code', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/send-activation-code', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async sendActivationCode(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body)
}
@httpPatch('/:userId', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPatch('/:userId', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async updateUser(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -47,7 +48,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpPut('/:userUuid/password', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPut('/:userUuid/password', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async changePassword(request: Request, response: Response): Promise<void> {
this.logger.debug(
'[DEPRECATED] use endpoint /v1/users/:userUuid/attributes/credentials instead of /v1/users/:userUuid/password',
@@ -65,7 +66,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpPut('/:userUuid/attributes/credentials', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPut('/:userUuid/attributes/credentials', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async changeCredentials(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -79,7 +80,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userId/params', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/:userId/params', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getKeyParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -88,12 +89,12 @@ export class UsersController extends BaseHttpController {
)
}
@all('/:userId/mfa', TYPES.RequiredCrossServiceTokenMiddleware)
@all('/:userId/mfa', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async blockMFA(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@httpPost('/:userUuid/integrations/listed', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/:userUuid/integrations/listed', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async createListedAccount(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -113,7 +114,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userUuid/settings', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/:userUuid/settings', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async listSettings(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -126,7 +127,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpPut('/:userUuid/settings', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPut('/:userUuid/settings', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async putSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -140,7 +141,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userUuid/settings/:settingName', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/:userUuid/settings/:settingName', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -154,7 +155,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpDelete('/:userUuid/settings/:settingName', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/:userUuid/settings/:settingName', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async deleteSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -169,7 +170,10 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userUuid/subscription-settings/:subscriptionSettingName', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet(
'/:userUuid/subscription-settings/:subscriptionSettingName',
TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware,
)
async getSubscriptionSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -183,7 +187,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userUuid/features', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/:userUuid/features', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -196,7 +200,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/:userUuid/subscription', TYPES.RequiredCrossServiceTokenMiddleware)
@httpGet('/:userUuid/subscription', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
@@ -209,7 +213,7 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/subscription', TYPES.SubscriptionTokenAuthMiddleware)
@httpGet('/subscription', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> {
if (response.locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
await this.httpService.callAuthServer(
@@ -232,12 +236,20 @@ export class UsersController extends BaseHttpController {
)
}
@httpDelete('/:userUuid', TYPES.RequiredCrossServiceTokenMiddleware)
@httpDelete('/:userUuid', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async deleteUser(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body)
if (!this.isConfiguredForHomeServer) {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body, true)
}
await this.httpService.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('DELETE', 'users/:userUuid', request.params.userUuid),
)
}
@httpPost('/:userUuid/requests', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/:userUuid/requests', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async submitRequest(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,

View File

@@ -10,14 +10,14 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v1/sockets')
export class WebSocketsController extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.Logger) private logger: Logger,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
) {
super()
}
@httpPost('/tokens', TYPES.RequiredCrossServiceTokenMiddleware)
@httpPost('/tokens', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
await this.httpService.callWebSocketServer(
request,
@@ -27,7 +27,7 @@ export class WebSocketsController extends BaseHttpController {
)
}
@httpPost('/connections', TYPES.WebSocketAuthMiddleware)
@httpPost('/connections', TYPES.ApiGateway_WebSocketAuthMiddleware)
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {
this.logger.error('Could not create a websocket connection. Missing connection id header.')

View File

@@ -9,8 +9,8 @@ import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolv
@controller('/v2')
export class ActionsControllerV2 extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private serviceProxy: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}
@@ -25,7 +25,7 @@ export class ActionsControllerV2 extends BaseHttpController {
)
}
@httpPost('/login-params', TYPES.OptionalCrossServiceTokenMiddleware)
@httpPost('/login-params', TYPES.ApiGateway_OptionalCrossServiceTokenMiddleware)
async loginParams(request: Request, response: Response): Promise<void> {
await this.serviceProxy.callAuthServer(
request,

View File

@@ -6,7 +6,7 @@ import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
@controller('/v2')
export class PaymentsControllerV2 extends BaseHttpController {
constructor(@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface) {
constructor(@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface) {
super()
}
@@ -15,22 +15,22 @@ export class PaymentsControllerV2 extends BaseHttpController {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/tailored', TYPES.SubscriptionTokenAuthMiddleware)
@httpGet('/subscriptions/tailored', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async getTailoredSubscriptionsWithFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/deltas', TYPES.SubscriptionTokenAuthMiddleware)
@httpGet('/subscriptions/deltas', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async getSubscriptionDeltasForChangingPlan(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas', request.body)
}
@httpPost('/subscriptions/deltas/apply', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/subscriptions/deltas/apply', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async applySubscriptionDelta(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas/apply', request.body)
}
@httpPost('/subscriptions/change-payment-method', TYPES.SubscriptionTokenAuthMiddleware)
@httpPost('/subscriptions/change-payment-method', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async changePaymentMethod(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
@@ -40,7 +40,7 @@ export class PaymentsControllerV2 extends BaseHttpController {
)
}
@httpGet('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
@httpGet('/subscriptions/:subscriptionId', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
@@ -50,7 +50,7 @@ export class PaymentsControllerV2 extends BaseHttpController {
)
}
@httpDelete('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
@httpDelete('/subscriptions/:subscriptionId', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async cancelSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
@@ -60,7 +60,7 @@ export class PaymentsControllerV2 extends BaseHttpController {
)
}
@httpPatch('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
@httpPatch('/subscriptions/:subscriptionId', TYPES.ApiGateway_SubscriptionTokenAuthMiddleware)
async updateSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,

View File

@@ -6,11 +6,11 @@ import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v2/items/:itemUuid/revisions', TYPES.RequiredCrossServiceTokenMiddleware)
@controller('/v2/items/:itemUuid/revisions', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
export class RevisionsControllerV2 extends BaseHttpController {
constructor(
@inject(TYPES.ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
) {
super()
}

View File

@@ -9,7 +9,7 @@ export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterf
private readonly PREFIX = 'cst'
private readonly USER_CST_PREFIX = 'user-cst'
constructor(@inject(TYPES.Redis) private redisClient: IORedis.Redis) {}
constructor(@inject(TYPES.ApiGateway_Redis) private redisClient: IORedis.Redis) {}
async set(dto: {
authorizationHeaderValue: string

View File

@@ -11,17 +11,17 @@ import { ServiceProxyInterface } from './ServiceProxyInterface'
@injectable()
export class HttpServiceProxy implements ServiceProxyInterface {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.SYNCING_SERVER_JS_URL) private syncingServerJsUrl: string,
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Logger) private logger: Logger,
@inject(TYPES.ApiGateway_HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.ApiGateway_AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.ApiGateway_SYNCING_SERVER_JS_URL) private syncingServerJsUrl: string,
@inject(TYPES.ApiGateway_PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
@inject(TYPES.ApiGateway_FILES_SERVER_URL) private filesServerUrl: string,
@inject(TYPES.ApiGateway_WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
@inject(TYPES.ApiGateway_REVISIONS_SERVER_URL) private revisionsServerUrl: string,
@inject(TYPES.ApiGateway_EMAIL_SERVER_URL) private emailServerUrl: string,
@inject(TYPES.ApiGateway_HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.ApiGateway_CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
) {}
async validateSession(
@@ -130,19 +130,29 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>> {
if (!this.paymentsServerUrl) {
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
return
}
await this.callServerWithLegacyFormat(
const requestDuplicate = Object.assign({}, request)
const responseDuplicate = Object.assign({}, response)
const rawResponse = await this.callServerWithLegacyFormat(
this.paymentsServerUrl,
request,
response,
returnRawResponse ? requestDuplicate : request,
returnRawResponse ? responseDuplicate : response,
endpointOrMethodIdentifier,
payload,
returnRawResponse,
)
if (returnRawResponse) {
return rawResponse
}
}
async callAuthServerWithLegacyFormat(
@@ -279,7 +289,8 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>> {
const serviceResponse = await this.getServerResponse(
serverUrl,
request,
@@ -295,9 +306,21 @@ export class HttpServiceProxy implements ServiceProxyInterface {
this.applyResponseHeaders(serviceResponse, response)
if (serviceResponse.request._redirectable._redirectCount > 0) {
response.status(302).redirect(serviceResponse.request.res.responseUrl)
response.status(302)
if (returnRawResponse) {
return response
}
response.redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status).send(serviceResponse.data)
response.status(serviceResponse.status)
if (returnRawResponse) {
return response
}
response.send(serviceResponse.data)
}
}

View File

@@ -42,7 +42,8 @@ export interface ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void>
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>>
callWebSocketServer(
request: Request,
response: Response,

View File

@@ -43,6 +43,7 @@ export class EndpointResolver implements EndpointResolverInterface {
['[PATCH]:users/:userId', 'auth.users.update'],
['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'],
['[PUT]:auth/params', 'auth.users.getKeyParams'],
['[DELETE]:users/:userUuid', 'auth.users.delete'],
['[POST]:listed', 'auth.users.createListedAccount'],
['[POST]:auth', 'auth.users.register'],
['[GET]:users/:userUuid/settings', 'auth.users.getSettings'],

View File

@@ -30,7 +30,9 @@ describe('AuthenticationMethodResolver', () => {
user = {} as jest.Mocked<User>
session = {} as jest.Mocked<Session>
session = {
userUuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<Session>
revokedSession = {} as jest.Mocked<RevokedSession>
@@ -50,17 +52,23 @@ describe('AuthenticationMethodResolver', () => {
})
it('should resolve jwt authentication method', async () => {
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: '123' })
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: '00000000-0000-0000-0000-000000000000' })
expect(await createResolver().resolve('test')).toEqual({
claims: {
user_uuid: '123',
user_uuid: '00000000-0000-0000-0000-000000000000',
},
type: 'jwt',
user,
})
})
it('should not resolve jwt authentication method with invalid user uuid', async () => {
sessionTokenDecoder.decodeToken = jest.fn().mockReturnValue({ user_uuid: 'invalid' })
expect(await createResolver().resolve('test')).toBeUndefined
})
it('should resolve session authentication method', async () => {
sessionService.getSessionFromToken = jest.fn().mockReturnValue(session)
@@ -71,6 +79,12 @@ describe('AuthenticationMethodResolver', () => {
})
})
it('should not resolve session authentication method with invalid user uuid on session', async () => {
sessionService.getSessionFromToken = jest.fn().mockReturnValue({ userUuid: 'invalid' })
expect(await createResolver().resolve('test')).toBeUndefined
})
it('should resolve archvied session authentication method', async () => {
sessionService.getRevokedSessionFromToken = jest.fn().mockReturnValue(revokedSession)

View File

@@ -6,6 +6,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { AuthenticationMethod } from './AuthenticationMethod'
import { AuthenticationMethodResolverInterface } from './AuthenticationMethodResolverInterface'
import { Logger } from 'winston'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class AuthenticationMethodResolver implements AuthenticationMethodResolverInterface {
@@ -29,9 +30,15 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
if (decodedToken) {
this.logger.debug('Token decoded successfully. User found.')
const userUuidOrError = Uuid.create(decodedToken.user_uuid as string)
if (userUuidOrError.isFailed()) {
return undefined
}
const userUuid = userUuidOrError.getValue()
return {
type: 'jwt',
user: await this.userRepository.findOneByUuid(<string>decodedToken.user_uuid),
user: await this.userRepository.findOneByUuid(userUuid),
claims: decodedToken,
}
}
@@ -40,9 +47,15 @@ export class AuthenticationMethodResolver implements AuthenticationMethodResolve
if (session) {
this.logger.debug('Token decoded successfully. Session found.')
const userUuidOrError = Uuid.create(session.userUuid)
if (userUuidOrError.isFailed()) {
return undefined
}
const userUuid = userUuidOrError.getValue()
return {
type: 'session_token',
user: await this.userRepository.findOneByUuid(session.userUuid),
user: await this.userRepository.findOneByUuid(userUuid),
session: session,
}
}

View File

@@ -50,7 +50,7 @@ describe('AccountDeletionRequestedEventHandler', () => {
ephemeralSession = {
uuid: '2-3-4',
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<EphemeralSession>
ephemeralSessionRepository = {} as jest.Mocked<EphemeralSessionRepositoryInterface>
@@ -68,7 +68,7 @@ describe('AccountDeletionRequestedEventHandler', () => {
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
userCreatedAtTimestamp: 1,
regularSubscriptionUuid: '2-3-4',
}
@@ -84,6 +84,14 @@ describe('AccountDeletionRequestedEventHandler', () => {
expect(userRepository.remove).toHaveBeenCalledWith(user)
})
it('should not remove a user with invalid uuid', async () => {
event.payload.userUuid = 'invalid'
await createHandler().handle(event)
expect(userRepository.remove).not.toHaveBeenCalled()
})
it('should not remove a user if one does not exist', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
@@ -100,6 +108,6 @@ describe('AccountDeletionRequestedEventHandler', () => {
expect(sessionRepository.remove).toHaveBeenCalledWith(session)
expect(revokedSessionRepository.remove).toHaveBeenCalledWith(revokedSession)
expect(ephemeralSessionRepository.deleteOne).toHaveBeenCalledWith('2-3-4', '1-2-3')
expect(ephemeralSessionRepository.deleteOne).toHaveBeenCalledWith('2-3-4', '00000000-0000-0000-0000-000000000000')
})
})

View File

@@ -6,6 +6,7 @@ import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSession
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
@@ -19,19 +20,27 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
const user = await this.userRepository.findOneByUuid(event.payload.userUuid)
if (user === null) {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Could not find user with uuid: ${event.payload.userUuid}`)
return
}
const userUuid = userUuidOrError.getValue()
await this.removeSessions(event.payload.userUuid)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
return
}
await this.removeSessions(userUuid.value)
await this.userRepository.remove(user)
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
}
private async removeSessions(userUuid: string): Promise<void> {

View File

@@ -33,7 +33,7 @@ describe('FileUploadedEventHandler', () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
regularSubscription = {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: UserSubscriptionType.Regular,
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
@@ -56,9 +56,9 @@ describe('FileUploadedEventHandler', () => {
event = {} as jest.Mocked<FileUploadedEvent>
event.createdAt = new Date(1)
event.payload = {
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
fileByteSize: 123,
filePath: '1-2-3/2-3-4',
filePath: '00000000-0000-0000-0000-000000000000/2-3-4',
fileName: '2-3-4',
}
@@ -78,13 +78,21 @@ describe('FileUploadedEventHandler', () => {
},
user,
userSubscription: {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: 'regular',
user: Promise.resolve(user),
},
})
})
it('should not do anything if a user uuid is invalid', async () => {
event.payload.userUuid = 'invalid'
await createHandler().handle(event)
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
it('should not do anything if a user is not found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
@@ -121,7 +129,7 @@ describe('FileUploadedEventHandler', () => {
},
user,
userSubscription: {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: 'regular',
user: Promise.resolve(user),
},
@@ -147,7 +155,7 @@ describe('FileUploadedEventHandler', () => {
},
user,
userSubscription: {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: 'regular',
user: Promise.resolve(user),
},

View File

@@ -10,6 +10,7 @@ import { UserSubscription } from '../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../Subscription/UserSubscriptionServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { User } from '../User/User'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class FileUploadedEventHandler implements DomainEventHandlerInterface {
@@ -22,17 +23,25 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
) {}
async handle(event: FileUploadedEvent): Promise<void> {
const user = await this.userRepository.findOneByUuid(event.payload.userUuid)
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(userUuidOrError.getError())
return
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${event.payload.userUuid}`)
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
return
}
const { regularSubscription, sharedSubscription } =
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(event.payload.userUuid)
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(userUuid.value)
if (regularSubscription === null) {
this.logger.warn(`Could not find regular user subscription for user with uuid: ${event.payload.userUuid}`)
this.logger.warn(`Could not find regular user subscription for user with uuid: ${userUuid.value}`)
return
}

View File

@@ -214,13 +214,25 @@ describe('RoleService', () => {
})
it('should indicate if a user has given permission', async () => {
const userHasPermission = await createService().userHasPermission('1-2-3', PermissionName.DailyEmailBackup)
const userHasPermission = await createService().userHasPermission(
'00000000-0000-0000-0000-000000000000',
PermissionName.DailyEmailBackup,
)
expect(userHasPermission).toBeTruthy()
})
it('should not indiciate if a user has permission if the user uuid is invalid', async () => {
const userHasPermission = await createService().userHasPermission('invalid', PermissionName.DailyEmailBackup)
expect(userHasPermission).toBeFalsy()
})
it('should indicate if a user does not have a given permission', async () => {
const userHasPermission = await createService().userHasPermission('1-2-3', PermissionName.MarkdownProEditor)
const userHasPermission = await createService().userHasPermission(
'00000000-0000-0000-0000-000000000000',
PermissionName.MarkdownProEditor,
)
expect(userHasPermission).toBeFalsy()
})
@@ -228,7 +240,10 @@ describe('RoleService', () => {
it('should indicate user does not have a permission if user could not be found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const userHasPermission = await createService().userHasPermission('1-2-3', PermissionName.MarkdownProEditor)
const userHasPermission = await createService().userHasPermission(
'00000000-0000-0000-0000-000000000000',
PermissionName.MarkdownProEditor,
)
expect(userHasPermission).toBeFalsy()
})

View File

@@ -13,6 +13,7 @@ import { RoleToSubscriptionMapInterface } from './RoleToSubscriptionMapInterface
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { Role } from './Role'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class RoleService implements RoleServiceInterface {
@@ -26,10 +27,16 @@ export class RoleService implements RoleServiceInterface {
@inject(TYPES.Auth_Logger) private logger: Logger,
) {}
async userHasPermission(userUuid: string, permissionName: PermissionName): Promise<boolean> {
async userHasPermission(userUuidString: string, permissionName: PermissionName): Promise<boolean> {
const userUuidOrError = Uuid.create(userUuidString)
if (userUuidOrError.isFailed()) {
return false
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid ${userUuid} for permissions check`)
this.logger.warn(`Could not find user with uuid ${userUuid.value} for permissions check`)
return false
}

View File

@@ -32,7 +32,9 @@ describe('SettingDecrypter', () => {
serverEncryptionVersion: EncryptionVersion.Default,
} as jest.Mocked<Setting>
expect(await createDecrypter().decryptSettingValue(setting, '1-2-3')).toEqual('decrypted')
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual(
'decrypted',
)
})
it('should return null if the setting value is null', async () => {
@@ -41,7 +43,7 @@ describe('SettingDecrypter', () => {
serverEncryptionVersion: EncryptionVersion.Default,
} as jest.Mocked<Setting>
expect(await createDecrypter().decryptSettingValue(setting, '1-2-3')).toBeNull()
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toBeNull()
})
it('should return unencrypted value if the setting value is unencrypted', async () => {
@@ -50,7 +52,7 @@ describe('SettingDecrypter', () => {
serverEncryptionVersion: EncryptionVersion.Unencrypted,
} as jest.Mocked<Setting>
expect(await createDecrypter().decryptSettingValue(setting, '1-2-3')).toEqual('test')
expect(await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')).toEqual('test')
})
it('should throw if the user could not be found', async () => {
@@ -62,7 +64,23 @@ describe('SettingDecrypter', () => {
let caughtError = null
try {
await createDecrypter().decryptSettingValue(setting, '1-2-3')
await createDecrypter().decryptSettingValue(setting, '00000000-0000-0000-0000-000000000000')
} catch (error) {
caughtError = error
}
expect(caughtError).not.toBeNull()
})
it('should throw if the user uuid is invalid', async () => {
const setting = {
value: 'encrypted',
serverEncryptionVersion: EncryptionVersion.Default,
} as jest.Mocked<Setting>
let caughtError = null
try {
await createDecrypter().decryptSettingValue(setting, 'invalid')
} catch (error) {
caughtError = error
}

View File

@@ -6,6 +6,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Setting } from './Setting'
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
import { SubscriptionSetting } from './SubscriptionSetting'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class SettingDecrypter implements SettingDecrypterInterface {
@@ -14,12 +15,18 @@ export class SettingDecrypter implements SettingDecrypterInterface {
@inject(TYPES.Auth_Crypter) private crypter: CrypterInterface,
) {}
async decryptSettingValue(setting: Setting | SubscriptionSetting, userUuid: string): Promise<string | null> {
async decryptSettingValue(setting: Setting | SubscriptionSetting, userUuidString: string): Promise<string | null> {
if (setting.value !== null && setting.serverEncryptionVersion === EncryptionVersion.Default) {
const userUuidOrError = Uuid.create(userUuidString)
if (userUuidOrError.isFailed()) {
throw new Error(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
throw new Error(`Could not find user with uuid: ${userUuid}`)
throw new Error(`Could not find user with uuid: ${userUuid.value}`)
}
return this.crypter.decryptForUser(setting.value, user)

View File

@@ -17,7 +17,7 @@ describe('AuthenticateSubscriptionToken', () => {
beforeEach(() => {
subscriptionTokenRepository = {} as jest.Mocked<SubscriptionTokenRepositoryInterface>
subscriptionTokenRepository.getUserUuidByToken = jest.fn().mockReturnValue('1-2-3')
subscriptionTokenRepository.getUserUuidByToken = jest.fn().mockReturnValue('00000000-0000-0000-0000-000000000000')
user = {
roles: Promise.resolve([{ name: RoleName.NAMES.CoreUser }]),
@@ -30,8 +30,6 @@ describe('AuthenticateSubscriptionToken', () => {
it('should authenticate an subscription token', async () => {
const response = await createUseCase().execute({ token: 'test' })
expect(userRepository.findOneByUuid).toHaveBeenCalledWith('1-2-3')
expect(response.success).toBeTruthy()
expect(response.user).toEqual(user)

View File

@@ -6,6 +6,7 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { AuthenticateSubscriptionTokenDTO } from './AuthenticateSubscriptionTokenDTO'
import { AuthenticateSubscriptionTokenResponse } from './AuthenticateSubscriptionTokenResponse'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class AuthenticateSubscriptionToken implements UseCaseInterface {
@@ -16,12 +17,14 @@ export class AuthenticateSubscriptionToken implements UseCaseInterface {
) {}
async execute(dto: AuthenticateSubscriptionTokenDTO): Promise<AuthenticateSubscriptionTokenResponse> {
const userUuid = await this.subscriptionTokenRepository.getUserUuidByToken(dto.token)
if (userUuid === undefined) {
const userUuidString = await this.subscriptionTokenRepository.getUserUuidByToken(dto.token)
const userUuidOrError = Uuid.create(userUuidString as string)
if (userUuidOrError.isFailed()) {
return {
success: false,
}
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {

View File

@@ -28,13 +28,15 @@ describe('CreateCrossServiceToken', () => {
session = {} as jest.Mocked<Session>
user = {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
email: 'test@test.te',
} as jest.Mocked<User>
user.roles = Promise.resolve([role])
userProjector = {} as jest.Mocked<ProjectorInterface<User>>
userProjector.projectSimple = jest.fn().mockReturnValue({ uuid: '1-2-3', email: 'test@test.te' })
userProjector.projectSimple = jest
.fn()
.mockReturnValue({ uuid: '00000000-0000-0000-0000-000000000000', email: 'test@test.te' })
roleProjector = {} as jest.Mocked<ProjectorInterface<Role>>
roleProjector.projectSimple = jest.fn().mockReturnValue({ name: 'role1', uuid: '1-3-4' })
@@ -69,7 +71,7 @@ describe('CreateCrossServiceToken', () => {
},
user: {
email: 'test@test.te',
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
},
},
60,
@@ -91,7 +93,7 @@ describe('CreateCrossServiceToken', () => {
],
user: {
email: 'test@test.te',
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
},
},
60,
@@ -100,7 +102,7 @@ describe('CreateCrossServiceToken', () => {
it('should create a cross service token for user by user uuid', async () => {
await createUseCase().execute({
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
@@ -113,7 +115,7 @@ describe('CreateCrossServiceToken', () => {
],
user: {
email: 'test@test.te',
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
},
},
60,
@@ -126,7 +128,20 @@ describe('CreateCrossServiceToken', () => {
let caughtError = null
try {
await createUseCase().execute({
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
})
} catch (error) {
caughtError = error
}
expect(caughtError).not.toBeNull()
})
it('should throw an error if user uuid is invalid', async () => {
let caughtError = null
try {
await createUseCase().execute({
userUuid: 'invalid',
})
} catch (error) {
caughtError = error

View File

@@ -11,6 +11,7 @@ import { UseCaseInterface } from '../UseCaseInterface'
import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
import { CreateCrossServiceTokenResponse } from './CreateCrossServiceTokenResponse'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class CreateCrossServiceToken implements UseCaseInterface {
@@ -26,7 +27,13 @@ export class CreateCrossServiceToken implements UseCaseInterface {
async execute(dto: CreateCrossServiceTokenDTO): Promise<CreateCrossServiceTokenResponse> {
let user: User | undefined | null = dto.user
if (user === undefined && dto.userUuid !== undefined) {
user = await this.userRepository.findOneByUuid(dto.userUuid)
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
user = await this.userRepository.findOneByUuid(userUuid)
}
if (!user) {

View File

@@ -34,7 +34,7 @@ describe('DeleteAccount', () => {
} as jest.Mocked<UserSubscription>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(user)
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
userSubscriptionService = {} as jest.Mocked<UserSubscriptionServiceInterface>
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
@@ -58,7 +58,7 @@ describe('DeleteAccount', () => {
.fn()
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
expect(await createUseCase().execute({ email: 'test@test.te' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
message: 'Successfully deleted user',
responseCode: 200,
success: true,
@@ -77,7 +77,7 @@ describe('DeleteAccount', () => {
.fn()
.mockReturnValue({ regularSubscription, sharedSubscription: null })
expect(await createUseCase().execute({ email: 'test@test.te' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
message: 'Successfully deleted user',
responseCode: 200,
success: true,
@@ -92,9 +92,9 @@ describe('DeleteAccount', () => {
})
it('should not trigger account deletion if user is not found', async () => {
userRepository.findOneByUsernameOrEmail = jest.fn().mockReturnValue(null)
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
expect(await createUseCase().execute({ email: 'test@test.te' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
message: 'User not found',
responseCode: 404,
success: false,
@@ -104,9 +104,9 @@ describe('DeleteAccount', () => {
expect(domainEventFactory.createAccountDeletionRequestedEvent).not.toHaveBeenCalled()
})
it('should not trigger account deletion if username is invalid', async () => {
expect(await createUseCase().execute({ email: '' })).toEqual({
message: 'Username cannot be empty',
it('should not trigger account deletion if user uuid is invalid', async () => {
expect(await createUseCase().execute({ userUuid: '' })).toEqual({
message: 'Given value is not a valid uuid: ',
responseCode: 400,
success: false,
})

View File

@@ -1,4 +1,4 @@
import { Username } from '@standardnotes/domain-core'
import { Uuid } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
@@ -23,17 +23,17 @@ export class DeleteAccount implements UseCaseInterface {
) {}
async execute(dto: DeleteAccountDTO): Promise<DeleteAccountResponse> {
const usernameOrError = Username.create(dto.email)
if (usernameOrError.isFailed()) {
const uuidOrError = Uuid.create(dto.userUuid)
if (uuidOrError.isFailed()) {
return {
success: false,
responseCode: 400,
message: usernameOrError.getError(),
message: uuidOrError.getError(),
}
}
const username = usernameOrError.getValue()
const uuid = uuidOrError.getValue()
const user = await this.userRepository.findOneByUsernameOrEmail(username)
const user = await this.userRepository.findOneByUuid(uuid)
if (user === null) {
return {

View File

@@ -1,3 +1,3 @@
export type DeleteAccountDTO = {
email: string
userUuid: string
}

View File

@@ -20,7 +20,7 @@ export class DeleteAuthenticator implements UseCaseInterface<string> {
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid.value)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('Could not delete authenticator: user not found.')
}

View File

@@ -37,7 +37,7 @@ export class GenerateAuthenticatorRegistrationOptions
}
const username = usernameOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid.value)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('Could not generate authenticator registration options: user not found.')
}

View File

@@ -19,7 +19,7 @@ export class GenerateRecoveryCodes implements UseCaseInterface<string> {
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid.value)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('Could not generate recovery codes: user not found')
}

View File

@@ -45,6 +45,10 @@ describe('GetSettings', () => {
)
beforeEach(() => {
user = {
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<User>
setting = new Setting()
setting.name = 'test'
setting.updatedAt = 345
@@ -90,8 +94,6 @@ describe('GetSettings', () => {
subscriptionSettingProjector = {} as jest.Mocked<SubscriptionSettingProjector>
subscriptionSettingProjector.projectSimple = jest.fn().mockReturnValue({ foo: 'sub-bar' })
user = {} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
@@ -103,18 +105,27 @@ describe('GetSettings', () => {
it('should fail if a user is not found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: false,
error: {
message: 'User 1-2-3 not found.',
message: 'User 00000000-0000-0000-0000-000000000000 not found.',
},
})
})
it('should fail if a user uuid is invalid', async () => {
expect(await createUseCase().execute({ userUuid: 'invalid' })).toEqual({
success: false,
error: {
message: 'Given value is not a valid uuid: invalid',
},
})
})
it('should return all user settings except mfa', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }],
})
@@ -131,9 +142,9 @@ describe('GetSettings', () => {
} as jest.Mocked<Setting>
settingRepository.findAllByUserUuid = jest.fn().mockReturnValue([setting])
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }],
})
@@ -147,10 +158,14 @@ describe('GetSettings', () => {
it('should return all user settings of certain name', async () => {
expect(
await createUseCase().execute({ userUuid: '1-2-3', settingName: 'test', allowSensitiveRetrieval: true }),
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
settingName: 'test',
allowSensitiveRetrieval: true,
}),
).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }],
})
@@ -159,10 +174,14 @@ describe('GetSettings', () => {
it('should return all user settings updated after', async () => {
expect(
await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true, updatedAfter: 123 }),
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
allowSensitiveRetrieval: true,
updatedAfter: 123,
}),
).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }],
})
@@ -170,9 +189,14 @@ describe('GetSettings', () => {
})
it('should return all sensitive user settings if explicit', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3', allowSensitiveRetrieval: true })).toEqual({
expect(
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
allowSensitiveRetrieval: true,
}),
).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }, { foo: 'bar' }],
})
@@ -190,9 +214,9 @@ describe('GetSettings', () => {
})
it('should return all user settings except mfa', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }, { foo: 'sub-bar' }],
})
@@ -210,9 +234,9 @@ describe('GetSettings', () => {
})
it('should return all user settings except mfa', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: true,
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
settings: [{ foo: 'bar' }, { foo: 'sub-bar' }],
})

View File

@@ -15,6 +15,7 @@ import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
import { SimpleSetting } from '../../Setting/SimpleSetting'
import { SimpleSubscriptionSetting } from '../../Setting/SimpleSubscriptionSetting'
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class GetSettings implements UseCaseInterface {
@@ -30,7 +31,16 @@ export class GetSettings implements UseCaseInterface {
) {}
async execute(dto: GetSettingsDto): Promise<GetSettingsResponse> {
const { userUuid } = dto
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return {
success: false,
error: {
message: userUuidOrError.getError(),
},
}
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
@@ -38,13 +48,13 @@ export class GetSettings implements UseCaseInterface {
return {
success: false,
error: {
message: `User ${userUuid} not found.`,
message: `User ${userUuid.value} not found.`,
},
}
}
let settings: Array<Setting | SubscriptionSetting>
settings = await this.settingRepository.findAllByUserUuid(userUuid)
settings = await this.settingRepository.findAllByUserUuid(user.uuid)
const { regularSubscription, sharedSubscription } =
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(user.uuid)
@@ -83,7 +93,7 @@ export class GetSettings implements UseCaseInterface {
return {
success: true,
userUuid,
userUuid: user.uuid,
settings: simpleSettings,
}
}

View File

@@ -31,23 +31,36 @@ describe('GetUserFeatures', () => {
it('should fail if a user is not found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
expect(await createUseCase().execute({ userUuid: 'user-1-1-1', offline: false })).toEqual({
success: false,
error: {
message: 'User user-1-1-1 not found.',
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000', offline: false })).toEqual(
{
success: false,
error: {
message: 'User 00000000-0000-0000-0000-000000000000 not found.',
},
},
})
)
})
it('should return user features', async () => {
expect(await createUseCase().execute({ userUuid: 'user-1-1-1', offline: false })).toEqual({
success: true,
userUuid: 'user-1-1-1',
features: [
{
name: 'foobar',
},
],
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000', offline: false })).toEqual(
{
success: true,
userUuid: '00000000-0000-0000-0000-000000000000',
features: [
{
name: 'foobar',
},
],
},
)
})
it('should fail if a user uuid is invalid', async () => {
expect(await createUseCase().execute({ userUuid: 'invalid', offline: false })).toEqual({
success: false,
error: {
message: 'Given value is not a valid uuid: invalid',
},
})
})

View File

@@ -5,6 +5,7 @@ import { GetUserFeaturesDto } from './GetUserFeaturesDto'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { GetUserFeaturesResponse } from './GetUserFeaturesResponse'
import { FeatureServiceInterface } from '../../Feature/FeatureServiceInterface'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class GetUserFeatures implements UseCaseInterface {
@@ -24,13 +25,24 @@ export class GetUserFeatures implements UseCaseInterface {
}
}
const user = await this.userRepository.findOneByUuid(dto.userUuid)
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return {
success: false,
error: {
message: userUuidOrError.getError(),
},
}
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return {
success: false,
error: {
message: `User ${dto.userUuid} not found.`,
message: `User ${userUuid.value} not found.`,
},
}
}
@@ -39,7 +51,7 @@ export class GetUserFeatures implements UseCaseInterface {
return {
success: true,
userUuid: dto.userUuid,
userUuid: userUuid.value,
features: userFeatures,
}
}

View File

@@ -61,7 +61,9 @@ describe('GetUserKeyParams', () => {
})
it('should get key params for an authenticated user - searching by uuid', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3', authenticated: true })).toEqual({
expect(
await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000', authenticated: true }),
).toEqual({
keyParams: {
foo: 'bar',
},
@@ -71,7 +73,9 @@ describe('GetUserKeyParams', () => {
})
it('should get key params for an unauthenticated user - searching by uuid', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3', authenticated: false })).toEqual({
expect(
await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000', authenticated: false }),
).toEqual({
keyParams: {
foo: 'bar',
},
@@ -81,7 +85,13 @@ describe('GetUserKeyParams', () => {
})
it("should get key params for an unauthenticated user and store it's code challenge", async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3', authenticated: false, codeChallenge: 'test' })).toEqual({
expect(
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticated: false,
codeChallenge: 'test',
}),
).toEqual({
keyParams: {
foo: 'bar',
},
@@ -107,7 +117,18 @@ describe('GetUserKeyParams', () => {
let error = null
try {
await createUseCase().execute({ userUuid: '1-2-3', authenticated: false })
await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000', authenticated: false })
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw error for an invalid user uuid', async () => {
let error = null
try {
await createUseCase().execute({ userUuid: 'invalid', authenticated: false })
} catch (e) {
error = e
}

View File

@@ -10,7 +10,7 @@ import { User } from '../../User/User'
import { PKCERepositoryInterface } from '../../User/PKCERepositoryInterface'
import { GetUserKeyParamsDTOV2Challenged } from './GetUserKeyParamsDTOV2Challenged'
import { KeyParamsData } from '@standardnotes/responses'
import { Username } from '@standardnotes/domain-core'
import { Username, Uuid } from '@standardnotes/domain-core'
@injectable()
export class GetUserKeyParams implements UseCaseInterface {
@@ -39,7 +39,13 @@ export class GetUserKeyParams implements UseCaseInterface {
}
}
} else if (dto.userUuid) {
user = await this.userRepository.findOneByUuid(dto.userUuid)
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
throw Error(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
user = await this.userRepository.findOneByUuid(userUuid)
}
if (!user) {

View File

@@ -15,7 +15,10 @@ describe('GetUserSubscription', () => {
const createUseCase = () => new GetUserSubscription(userRepository, userSubscriptionRepository)
beforeEach(() => {
user = { uuid: 'user-1-1-1', email: 'user-1-1-1@example.com' } as jest.Mocked<User>
user = {
uuid: '00000000-0000-0000-0000-000000000000',
email: '00000000-0000-0000-0000-000000000000@example.com',
} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
@@ -29,18 +32,27 @@ describe('GetUserSubscription', () => {
it('should fail if a user is not found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
expect(await createUseCase().execute({ userUuid: 'user-1-1-1' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: false,
error: {
message: 'User user-1-1-1 not found.',
message: 'User 00000000-0000-0000-0000-000000000000 not found.',
},
})
})
it('should fail if a user uuid is invalid', async () => {
expect(await createUseCase().execute({ userUuid: 'invalid' })).toEqual({
success: false,
error: {
message: 'Given value is not a valid uuid: invalid',
},
})
})
it('should return user subscription', async () => {
expect(await createUseCase().execute({ userUuid: 'user-1-1-1' })).toEqual({
expect(await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })).toEqual({
success: true,
user: { uuid: 'user-1-1-1', email: 'user-1-1-1@example.com' },
user: { uuid: '00000000-0000-0000-0000-000000000000', email: '00000000-0000-0000-0000-000000000000@example.com' },
subscription: {
planName: SubscriptionName.ProPlan,
},

View File

@@ -5,6 +5,7 @@ import { GetUserSubscriptionDto } from './GetUserSubscriptionDto'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { GetUserSubscriptionResponse } from './GetUserSubscriptionResponse'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class GetUserSubscription implements UseCaseInterface {
@@ -15,7 +16,16 @@ export class GetUserSubscription implements UseCaseInterface {
) {}
async execute(dto: GetUserSubscriptionDto): Promise<GetUserSubscriptionResponse> {
const { userUuid } = dto
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return {
success: false,
error: {
message: userUuidOrError.getError(),
},
}
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
@@ -23,12 +33,12 @@ export class GetUserSubscription implements UseCaseInterface {
return {
success: false,
error: {
message: `User ${userUuid} not found.`,
message: `User ${userUuid.value} not found.`,
},
}
}
const userSubscription = await this.userSubscriptionRepository.findOneByUserUuid(userUuid)
const userSubscription = await this.userSubscriptionRepository.findOneByUserUuid(userUuid.value)
return {
success: true,

View File

@@ -21,7 +21,7 @@ export class ListAuthenticators implements UseCaseInterface<Authenticator[]> {
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid.value)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('Could not list authenticators: user not found.')
}

View File

@@ -62,7 +62,7 @@ describe('UpdateSetting', () => {
settingProjector.projectSimple = jest.fn().mockReturnValue(settingProjection)
regularSubscription = {
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: UserSubscriptionType.Regular,
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
@@ -109,7 +109,7 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).toHaveBeenCalledWith({
props: {
@@ -138,19 +138,40 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 not found.',
message: 'User 00000000-0000-0000-0000-000000000000 not found.',
},
statusCode: 404,
})
})
it('should not create a setting if user uuid is invalid', async () => {
const props = {
name: SettingName.NAMES.ExtensionKey,
unencryptedValue: 'test-setting-value',
serverEncryptionVersion: EncryptionVersion.Unencrypted,
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: 'invalid' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'Given value is not a valid uuid: invalid',
},
statusCode: 400,
})
})
it('should not create a subscription setting', async () => {
const props = {
name: SettingName.NAMES.MuteSignInEmails,
@@ -159,14 +180,14 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 has no subscription to change a subscription setting.',
message: 'User 00000000-0000-0000-0000-000000000000 has no subscription to change a subscription setting.',
},
statusCode: 400,
})
@@ -180,7 +201,7 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
@@ -207,14 +228,14 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
message: 'User 00000000-0000-0000-0000-000000000000 is not permitted to change the setting.',
},
statusCode: 401,
})
@@ -230,14 +251,14 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(settingService.createOrReplace).not.toHaveBeenCalled()
expect(response).toEqual({
success: false,
error: {
message: 'User 1-2-3 is not permitted to change the setting.',
message: 'User 00000000-0000-0000-0000-000000000000 is not permitted to change the setting.',
},
statusCode: 401,
})
@@ -259,7 +280,7 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
@@ -295,7 +316,7 @@ describe('UpdateSetting', () => {
sensitive: false,
}
const response = await createUseCase().execute({ props, userUuid: '1-2-3' })
const response = await createUseCase().execute({ props, userUuid: '00000000-0000-0000-0000-000000000000' })
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {

View File

@@ -16,6 +16,7 @@ import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionS
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { CreateOrReplaceSubscriptionSettingResponse } from '../../Setting/CreateOrReplaceSubscriptionSettingResponse'
import { SubscriptionSettingProjector } from '../../../Projection/SubscriptionSettingProjector'
import { Uuid } from '@standardnotes/domain-core'
@injectable()
export class UpdateSetting implements UseCaseInterface {
@@ -48,7 +49,17 @@ export class UpdateSetting implements UseCaseInterface {
this.logger.debug('[%s] Updating setting: %O', dto.userUuid, dto)
const { userUuid, props } = dto
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return {
success: false,
error: {
message: userUuidOrError.getError(),
},
statusCode: 400,
}
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
@@ -56,7 +67,7 @@ export class UpdateSetting implements UseCaseInterface {
return {
success: false,
error: {
message: `User ${userUuid} not found.`,
message: `User ${userUuid.value} not found.`,
},
statusCode: 404,
}
@@ -66,14 +77,14 @@ export class UpdateSetting implements UseCaseInterface {
return {
success: false,
error: {
message: `User ${userUuid} is not permitted to change the setting.`,
message: `User ${userUuid.value} is not permitted to change the setting.`,
},
statusCode: 401,
}
}
props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting(settingName)
props.sensitive = this.settingsAssociationService.getSensitivityForSetting(settingName)
dto.props.serverEncryptionVersion = this.settingsAssociationService.getEncryptionVersionForSetting(settingName)
dto.props.sensitive = this.settingsAssociationService.getSensitivityForSetting(settingName)
if (settingName.isASubscriptionSetting()) {
const { regularSubscription, sharedSubscription } =
@@ -83,7 +94,7 @@ export class UpdateSetting implements UseCaseInterface {
return {
success: false,
error: {
message: `User ${userUuid} has no subscription to change a subscription setting.`,
message: `User ${userUuid.value} has no subscription to change a subscription setting.`,
},
statusCode: 400,
}
@@ -92,7 +103,7 @@ export class UpdateSetting implements UseCaseInterface {
const response = await this.subscriptionSettingService.createOrReplace({
userSubscription: subscription,
user,
props,
props: dto.props,
})
return {
@@ -104,7 +115,7 @@ export class UpdateSetting implements UseCaseInterface {
const response = await this.settingService.createOrReplace({
user,
props,
props: dto.props,
})
return {

View File

@@ -27,7 +27,7 @@ export class VerifyAuthenticatorRegistrationResponse implements UseCaseInterface
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid.value)
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('Could not verify authenticator registration response: user not found.')
}

View File

@@ -1,4 +1,4 @@
import { Email, Username } from '@standardnotes/domain-core'
import { Email, Username, Uuid } from '@standardnotes/domain-core'
import { ReadStream } from 'fs'
import { User } from './User'
@@ -6,7 +6,7 @@ import { User } from './User'
export interface UserRepositoryInterface {
streamAll(): Promise<ReadStream>
streamTeam(memberEmail?: Email): Promise<ReadStream>
findOneByUuid(uuid: string): Promise<User | null>
findOneByUuid(uuid: Uuid): Promise<User | null>
findOneByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User | null>
save(user: User): Promise<User>
remove(user: User): Promise<User>

View File

@@ -29,6 +29,7 @@ export class HomeServerUsersController extends BaseHttpController {
this.controllerContainer.register('auth.users.getKeyParams', this.keyParams.bind(this))
this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this))
this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
this.controllerContainer.register('auth.users.delete', this.deleteAccount.bind(this))
}
}
@@ -102,9 +103,20 @@ export class HomeServerUsersController extends BaseHttpController {
return this.json(result.keyParams)
}
async deleteAccount(request: Request): Promise<results.JsonResult> {
async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
if (request.params.userUuid !== response.locals.user.uuid) {
return this.json(
{
error: {
message: 'Operation not allowed.',
},
},
401,
)
}
const result = await this.doDeleteAccount.execute({
email: request.params.email,
userUuid: request.params.userUuid,
})
return this.json({ message: result.message }, result.responseCode)

View File

@@ -170,17 +170,34 @@ describe('InversifyExpressUsersController', () => {
})
it('should delete user', async () => {
request.params.email = 'test@test.te'
request.params.userUuid = '1-2-3'
response.locals.user = {
uuid: '1-2-3',
}
const httpResponse = <results.JsonResult>await createController().deleteAccount(request)
const httpResponse = <results.JsonResult>await createController().deleteAccount(request, response)
const result = await httpResponse.executeAsync()
expect(deleteAccount.execute).toHaveBeenCalledWith({ email: 'test@test.te' })
expect(deleteAccount.execute).toHaveBeenCalledWith({ userUuid: '1-2-3' })
expect(result.statusCode).toEqual(200)
expect(await result.content.readAsStringAsync()).toEqual('{"message":"A OK"}')
})
it('should not delete user if user uuid is different than the one in the session', async () => {
request.params.userUuid = '1-2-3'
response.locals.user = {
uuid: '2-3-4',
}
const httpResponse = <results.JsonResult>await createController().deleteAccount(request, response)
const result = await httpResponse.executeAsync()
expect(deleteAccount.execute).not.toHaveBeenCalled()
expect(result.statusCode).toEqual(401)
})
it('should get user key params', async () => {
request.query = {
email: 'test@test.te',

View File

@@ -51,9 +51,9 @@ export class InversifyExpressUsersController extends HomeServerUsersController {
return super.keyParams(request)
}
@httpDelete('/:email')
override async deleteAccount(request: Request): Promise<results.JsonResult> {
return super.deleteAccount(request)
@httpDelete('/:userUuid')
override async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteAccount(request, response)
}
@httpGet('/:userUuid/subscription', TYPES.Auth_RequiredCrossServiceTokenMiddleware)

View File

@@ -1,4 +1,4 @@
import { Email, Username } from '@standardnotes/domain-core'
import { Email, Username, Uuid } from '@standardnotes/domain-core'
import { ReadStream } from 'fs'
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
@@ -40,11 +40,11 @@ export class TypeORMUserRepository implements UserRepositoryInterface {
return queryBuilder.stream()
}
async findOneByUuid(uuid: string): Promise<User | null> {
async findOneByUuid(uuid: Uuid): Promise<User | null> {
return this.ormRepository
.createQueryBuilder('user')
.where('user.uuid = :uuid', { uuid })
.cache(`user_uuid_${uuid}`, 60000)
.where('user.uuid = :uuid', { uuid: uuid.value })
.cache(`user_uuid_${uuid.value}`, 60000)
.getOne()
}