From 99d2563545e8d5200831c506d97f069163e532da Mon Sep 17 00:00:00 2001 From: Konstantinos Feretos Date: Wed, 11 Oct 2023 11:49:50 +0300 Subject: [PATCH 1/3] feat(authentication): validateAccessToken grpc method --- modules/authentication/src/Authentication.ts | 39 +++++++++++++++++++ .../authentication/src/authentication.proto | 17 ++++++++ .../authentication/src/routes/middleware.ts | 2 +- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/authentication/src/Authentication.ts b/modules/authentication/src/Authentication.ts index ce00b2084..62e468061 100644 --- a/modules/authentication/src/Authentication.ts +++ b/modules/authentication/src/Authentication.ts @@ -27,6 +27,9 @@ import { GetTeamRequest, CreateTeamRequest, Team as GrpcTeam, + ValidateAccessTokenRequest, + ValidateAccessTokenResponse, + ValidateAccessTokenResponse_Status, } from './protoTypes/authentication'; import { runMigrations } from './migrations'; import metricsSchema from './metrics'; @@ -40,6 +43,7 @@ import { } from '@conduitplatform/module-tools'; import { TeamsAdmin } from './admin/team'; import { User as UserAuthz } from './authz'; +import { handleAuthentication } from './routes/middleware'; export default class Authentication extends ManagedModule { configSchema = AppConfigSchema; @@ -54,6 +58,7 @@ export default class Authentication extends ManagedModule { getTeam: this.getTeam.bind(this), createTeam: this.createTeam.bind(this), teamDelete: this.teamDelete.bind(this), + validateAccessToken: this.validateAccessToken.bind(this), }, }; protected metricsSchema = metricsSchema; @@ -373,6 +378,40 @@ export default class Authentication extends ManagedModule { return callback(null, { message: result as string }); } + async validateAccessToken( + call: GrpcRequest, + callback: GrpcCallback, + ) { + const accessToken = call.request.accessToken; + const path = call.request.path ?? ''; + let userId: string | undefined = undefined; + const accessStatus = await handleAuthentication( + {}, + { authorization: `Bearer ${accessToken}` }, + {}, + path, + ) + .then(r => { + userId = (r.user as models.User)._id; + return ValidateAccessTokenResponse_Status.AUTHENTICATED; + }) + .catch(err => { + switch (err.code as status) { + case status.PERMISSION_DENIED: + return ValidateAccessTokenResponse_Status.USER_BLOCKED; + case status.UNAUTHENTICATED: + if (err.message.includes('2FA')) { + return ValidateAccessTokenResponse_Status.REQUIRES_2FA; + } + // intentional fall through + default: + return ValidateAccessTokenResponse_Status.UNAUTHENTICATED; + } + }); + + return callback(null, { status: accessStatus, userId }); + } + protected registerSchemas() { const promises = Object.values(models).map(model => { const modelInstance = model.getInstance(this.database); diff --git a/modules/authentication/src/authentication.proto b/modules/authentication/src/authentication.proto index 4dce145d4..63c543593 100644 --- a/modules/authentication/src/authentication.proto +++ b/modules/authentication/src/authentication.proto @@ -59,6 +59,22 @@ message TeamDeleteResponse { string message = 1; } +message ValidateAccessTokenRequest { + string accessToken = 1; + optional string path = 2; +} + +message ValidateAccessTokenResponse { + enum Status { + AUTHENTICATED = 0; + UNAUTHENTICATED = 1; + REQUIRES_2FA = 2; + USER_BLOCKED = 3; + } + Status status = 1; + optional string userId = 2; +} + service Authentication { rpc UserLogin(UserLoginRequest) returns (UserLoginResponse); rpc UserCreate(UserCreateRequest) returns (UserCreateResponse); @@ -67,4 +83,5 @@ service Authentication { rpc GetTeam(GetTeamRequest) returns (Team); rpc CreateTeam(CreateTeamRequest) returns (Team); rpc TeamDelete(TeamDeleteRequest) returns (TeamDeleteResponse); + rpc ValidateAccessToken(ValidateAccessTokenRequest) returns (ValidateAccessTokenResponse); } diff --git a/modules/authentication/src/routes/middleware.ts b/modules/authentication/src/routes/middleware.ts index 3c8931ef4..9358709b6 100644 --- a/modules/authentication/src/routes/middleware.ts +++ b/modules/authentication/src/routes/middleware.ts @@ -38,7 +38,7 @@ export async function authMiddleware( return handleAuthentication(context, headers, cookies, call.request.path); } -async function handleAuthentication( +export async function handleAuthentication( context: Indexable, headers: Headers, cookies: Cookies, From 867fa0e0aa91077918196a418f8df59e9d6945a6 Mon Sep 17 00:00:00 2001 From: Konstantinos Feretos Date: Wed, 11 Oct 2023 12:02:34 +0300 Subject: [PATCH 2/3] chore: white space --- modules/authentication/src/Authentication.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/authentication/src/Authentication.ts b/modules/authentication/src/Authentication.ts index 62e468061..6a6f7dec4 100644 --- a/modules/authentication/src/Authentication.ts +++ b/modules/authentication/src/Authentication.ts @@ -408,7 +408,6 @@ export default class Authentication extends ManagedModule { return ValidateAccessTokenResponse_Status.UNAUTHENTICATED; } }); - return callback(null, { status: accessStatus, userId }); } From 7d14a53dfdaa3321d04694a9ed97fb284dfbc6b4 Mon Sep 17 00:00:00 2001 From: Konstantinos Feretos Date: Wed, 11 Oct 2023 12:56:39 +0300 Subject: [PATCH 3/3] feat(grpc-sdk): authentication.validateAccessToken() --- libraries/grpc-sdk/src/modules/authentication/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/grpc-sdk/src/modules/authentication/index.ts b/libraries/grpc-sdk/src/modules/authentication/index.ts index e1d7f9269..17fc8b39f 100644 --- a/libraries/grpc-sdk/src/modules/authentication/index.ts +++ b/libraries/grpc-sdk/src/modules/authentication/index.ts @@ -6,6 +6,7 @@ import { UserDeleteResponse, UserLoginResponse, Team, + ValidateAccessTokenResponse, } from '../../protoUtils'; export class Authentication extends ConduitModule { @@ -45,4 +46,11 @@ export class Authentication extends ConduitModule { return this.client!.teamDelete({ teamId }); } + + validateAccessToken( + accessToken: string, + path?: string, + ): Promise { + return this.client!.validateAccessToken({ accessToken, path }); + } }