Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions packages/client/src/context/Auth.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import jwt_decode from 'jwt-decode';
import * as firebaseui from 'firebaseui';
import * as firebase from '@firebase/app';
import * as firebaseauth from '@firebase/auth';
import { Organization } from '../graphql/graphql';
import { useGetOrganizationsQuery } from '../graphql/organization/organization';

const firebaseConfig = {
apiKey: import.meta.env.VITE_AUTH_API_KEY,
Expand All @@ -24,6 +26,7 @@ export interface DecodedToken {
};
sign_in_provider: string;
user_id: string;
tenant: string;
};
iat: number;
iss: string;
Expand All @@ -48,6 +51,16 @@ export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {
const [token, setToken] = useState<string | null>(localStorage.getItem(AUTH_TOKEN_STR));
const [authenticated, setAuthenticated] = useState<boolean>(true);
const [decodedToken, setDecodedToken] = useState<DecodedToken | null>(null);
const [organization, setOrganization] = useState<Organization | null>(null);

const getOrganizationResult = useGetOrganizationsQuery();

useEffect(() => {
// TODO: Handle multi-organization login
if (getOrganizationResult.data && getOrganizationResult.data.getOrganizations.length > 0) {
setOrganization(getOrganizationResult.data.getOrganizations[0]);
}
}, [getOrganizationResult.data]);

const handleUnauthenticated = () => {
// Clear the token and authenticated state
Expand Down Expand Up @@ -95,19 +108,27 @@ export const AuthProvider: FC<AuthProviderProps> = ({ children }) => {

return (
<AuthContext.Provider value={{ token, authenticated, decodedToken, logout }}>
{!authenticated && <FirebaseLoginWrapper setToken={handleAuthenticated} />}
{!authenticated && organization && (
<FirebaseLoginWrapper setToken={handleAuthenticated} organization={organization} />
)}
{authenticated && children}
</AuthContext.Provider>
);
};

interface FirebaseLoginWrapperProps {
setToken: (token: string) => void;
organization: Organization;
}

const FirebaseLoginWrapper: FC<FirebaseLoginWrapperProps> = ({ setToken }) => {
const FirebaseLoginWrapper: FC<FirebaseLoginWrapperProps> = ({ setToken, organization }) => {
firebase.initializeApp(firebaseConfig);
const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseauth.getAuth());

// Handle multi-tenant login
const auth = firebaseauth.getAuth();
auth.tenantId = organization.tenantID;

const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(auth);

const signInSuccess = async (authResult: any) => {
setToken(await authResult.user.getIdToken());
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/graphql/entry/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type EntryForDatasetQueryVariables = Types.Exact<{
}>;


export type EntryForDatasetQuery = { __typename?: 'Query', entryForDataset: Array<{ __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta: any, signedUrl: string, signedUrlExpiration: number }> };
export type EntryForDatasetQuery = { __typename?: 'Query', entryForDataset: Array<{ __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number }> };

export type DeleteEntryMutationVariables = Types.Exact<{
entry: Types.Scalars['ID']['input'];
Expand Down
6 changes: 5 additions & 1 deletion packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export type Entry = {
dataset: Scalars['ID']['output'];
dateCreated: Scalars['DateTime']['output'];
entryID: Scalars['String']['output'];
meta: Scalars['JSON']['output'];
meta?: Maybe<Scalars['JSON']['output']>;
organization: Scalars['ID']['output'];
signedUrl: Scalars['String']['output'];
/** Get the number of milliseconds the signed URL is valid for. */
Expand Down Expand Up @@ -468,13 +468,17 @@ export type Organization = {
/** URL where the user logs in against */
authURL: Scalars['String']['output'];
name: Scalars['String']['output'];
/** Tenant ID in the Identity Platform */
tenantID: Scalars['String']['output'];
};

export type OrganizationCreate = {
/** URL where the user logs in against */
authURL: Scalars['String']['input'];
name: Scalars['String']['input'];
projectId: Scalars['String']['input'];
/** Tenant ID in the Identity Platform */
tenantID: Scalars['String']['input'];
};

export type Permission = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ query getOrganizations {
getOrganizations {
_id,
name,
authURL
authURL,
tenantID
}
}
3 changes: 2 additions & 1 deletion packages/client/src/graphql/organization/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const defaultOptions = {} as const;
export type GetOrganizationsQueryVariables = Types.Exact<{ [key: string]: never; }>;


export type GetOrganizationsQuery = { __typename?: 'Query', getOrganizations: Array<{ __typename?: 'Organization', _id: string, name: string, authURL: string }> };
export type GetOrganizationsQuery = { __typename?: 'Query', getOrganizations: Array<{ __typename?: 'Organization', _id: string, name: string, authURL: string, tenantID: string }> };


export const GetOrganizationsDocument = gql`
Expand All @@ -17,6 +17,7 @@ export const GetOrganizationsDocument = gql`
_id
name
authURL
tenantID
}
}
`;
Expand Down
2 changes: 1 addition & 1 deletion packages/client/src/graphql/tag/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type AssignTagMutationVariables = Types.Exact<{
}>;


export type AssignTagMutation = { __typename?: 'Mutation', assignTag?: { __typename?: 'Tag', _id: string, entry: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta: any, signedUrl: string, signedUrlExpiration: number } } | null };
export type AssignTagMutation = { __typename?: 'Mutation', assignTag?: { __typename?: 'Tag', _id: string, entry: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number } } | null };

export type CompleteTagMutationVariables = Types.Exact<{
tag: Types.Scalars['ID']['input'];
Expand Down
4 changes: 1 addition & 3 deletions packages/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { SharedModule } from './shared/shared.module';
import { JwtModule } from './jwt/jwt.module';
import { PermissionModule } from './permission/permission.module';
import { AuthModule } from './auth/auth.module';
import { UserOrgModule } from './userorg/userorg.module';

@Module({
imports: [
Expand Down Expand Up @@ -45,8 +44,7 @@ import { UserOrgModule } from './userorg/userorg.module';
SharedModule,
JwtModule,
PermissionModule,
AuthModule,
UserOrgModule
AuthModule
]
})
export class AppModule {}
4 changes: 1 addition & 3 deletions packages/server/src/dataset/dataset.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import { PermissionModule } from '../permission/permission.module';
import { JwtModule } from '../jwt/jwt.module';
import { ProjectModule } from '../project/project.module';
import { OrganizationModule } from '../organization/organization.module';
import { UserOrgModule } from '../userorg/userorg.module';

@Module({
imports: [
MongooseModule.forFeature([{ name: Dataset.name, schema: DatasetSchema }]),
forwardRef(() => PermissionModule),
JwtModule,
ProjectModule,
OrganizationModule,
UserOrgModule
OrganizationModule
],
providers: [DatasetResolver, DatasetService, DatasetPipe],
exports: [DatasetService, DatasetPipe]
Expand Down
4 changes: 1 addition & 3 deletions packages/server/src/entry/entry.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { JwtModule } from '../jwt/jwt.module';
import { MongooseMiddlewareService } from '../shared/service/mongoose-callback.service';
import { SharedModule } from '../shared/shared.module';
import { OrganizationModule } from '../organization/organization.module';
import { UserOrgModule } from '../userorg/userorg.module';

@Module({
imports: [
Expand All @@ -45,8 +44,7 @@ import { UserOrgModule } from '../userorg/userorg.module';
GcpModule,
PermissionModule,
JwtModule,
OrganizationModule,
UserOrgModule
OrganizationModule
],
providers: [
EntryResolver,
Expand Down
2 changes: 0 additions & 2 deletions packages/server/src/jwt/jwt.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { HttpModule } from '@nestjs/axios';
import { JwtAuthGuard } from './jwt.guard';
import { JwtStrategy } from './jwt.strategy';
import { OrganizationModule } from '../organization/organization.module';
import { UserOrgModule } from '../userorg/userorg.module';
import { JwtSecretRequestType, JwtModule as NestJwtModule } from '@nestjs/jwt';

@Module({
imports: [
HttpModule,
forwardRef(() => OrganizationModule),
UserOrgModule,
NestJwtModule.registerAsync({
imports: [forwardRef(() => JwtModule)],
inject: [JwtService],
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/jwt/token.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface TokenPayload {
};
sign_in_provider: string;
user_id: string;
tenant: string;
};
iat: number;
iss: string;
Expand Down
29 changes: 3 additions & 26 deletions packages/server/src/organization/organization.guard.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
import { Injectable, ExecutionContext, CanActivate } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { OrganizationService } from './organization.service';
import { UserOrgService } from '../userorg/userorg.service';

@Injectable()
export class OrganizationGuard implements CanActivate {
constructor(
private readonly organizationService: OrganizationService,
private readonly userOrgService: UserOrgService
) {}
constructor(private readonly organizationService: OrganizationService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const user = ctx.getContext().req.user;

// Check for the organization in the headers
const organizationID = ctx.getContext().req.headers.organization;
if (organizationID == undefined || organizationID == 'undefined') {
return false;
}
if (typeof organizationID !== 'string') {
return false;
}

// Check if the organization exists
const organization = await this.organizationService.findOne(organizationID);
const organization = await this.organizationService.findByTenantID(user.firebase.tenant);
if (!organization) {
return false;
}

// Check to see if the user is in the organization
const user = ctx.getContext().req.user;
if (!user) {
return false;
}
const userOrg = await this.userOrgService.userIsInOrg(user.user_id, organizationID);
if (!userOrg) {
return false;
}

// Add the organization to the request
ctx.getContext().req.organization = organization;

Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/organization/organization.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export class Organization {
@Field()
name: string;

/** Maps the `projectId` in the auth service back to the organization */
@Prop()
projectId: string;
@Field({ description: 'Tenant ID in the Identity Platform' })
tenantID: string;

@Prop()
@Field({ description: 'URL where the user logs in against' })
Expand Down
3 changes: 1 addition & 2 deletions packages/server/src/organization/organization.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { OrganizationService } from './organization.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Organization, OrganizationSchema } from './organization.model';
import { CreateOrganizationPipe } from './pipes/create.pipe';
import { UserOrgModule } from '../userorg/userorg.module';

@Module({
imports: [MongooseModule.forFeature([{ name: Organization.name, schema: OrganizationSchema }]), UserOrgModule],
imports: [MongooseModule.forFeature([{ name: Organization.name, schema: OrganizationSchema }])],
providers: [OrganizationResolver, OrganizationService, CreateOrganizationPipe],
exports: [OrganizationService]
})
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/organization/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class OrganizationService {
return this.orgModel.findOne({ name });
}

async findByProject(projectId: string): Promise<Organization | null> {
return this.orgModel.findOne({ projectId });
async findByTenantID(tenantID: string): Promise<Organization | null> {
return this.orgModel.findOne({ tenantID });
}
}
4 changes: 1 addition & 3 deletions packages/server/src/permission/permission.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import { DatasetPermissionResolver } from './resolvers/dataset.resolver';
import { DatasetModule } from '../dataset/dataset.module';
import { PermissionResolver } from './resolvers/permission.resolver';
import { OrganizationModule } from '../organization/organization.module';
import { UserOrgModule } from '../userorg/userorg.module';

@Module({
imports: [
forwardRef(() => ProjectModule),
AuthModule,
forwardRef(() => StudyModule),
forwardRef(() => DatasetModule),
OrganizationModule,
UserOrgModule
OrganizationModule
],
providers: [
casbinProvider,
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/permission/resolvers/owner.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { Inject, UnauthorizedException, UseGuards } from '@nestjs/common';
import { Roles } from '../permissions/roles';
import { PermissionService } from '../permission.service';
import { JwtAuthGuard } from '../../jwt/jwt.guard';
import { OrganizationGuard } from '../../organization/organization.guard';

@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, OrganizationGuard)
@Resolver()
export class OwnerPermissionResolver {
constructor(
Expand Down
4 changes: 1 addition & 3 deletions packages/server/src/project/project.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { SharedModule } from 'src/shared/shared.module';
import { JwtModule } from '../jwt/jwt.module';
import { PermissionModule } from '../permission/permission.module';
import { OrganizationModule } from '../organization/organization.module';
import { UserOrgModule } from '../userorg/userorg.module';

@Module({
imports: [
Expand All @@ -32,8 +31,7 @@ import { UserOrgModule } from '../userorg/userorg.module';
]),
JwtModule,
forwardRef(() => PermissionModule),
OrganizationModule,
UserOrgModule
OrganizationModule
],
providers: [ProjectResolver, ProjectService, ProjectPipe],
exports: [ProjectPipe, ProjectService]
Expand Down
13 changes: 0 additions & 13 deletions packages/server/src/userorg/userorg.model.ts

This file was deleted.

12 changes: 0 additions & 12 deletions packages/server/src/userorg/userorg.module.ts

This file was deleted.

26 changes: 0 additions & 26 deletions packages/server/src/userorg/userorg.resolver.ts

This file was deleted.

Loading