feat: integrate cross-domain authentication to api

This commit is contained in:
Pujit Mehrotra
2024-10-11 10:45:03 -04:00
parent f6d09f4ba2
commit 754d4560ea
12 changed files with 88 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
[api]
version="3.11.0+3f537b97"
extraOrigins="https://google.com,https://test.com,http://localhost:4321"
version="3.11.0+04f7b636"
extraOrigins="https://google.com,https://test.com"
[local]
[notifier]
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"

View File

@@ -1,6 +1,6 @@
[api]
version="3.11.0+3f537b97"
extraOrigins="https://google.com,https://test.com,http://localhost:4321"
version="3.11.0+04f7b636"
extraOrigins="https://google.com,https://test.com"
[local]
[notifier]
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"
@@ -16,9 +16,9 @@ regWizTime="1611175408732_0951-1653-3509-FBA155FA23C0"
idtoken=""
accesstoken=""
refreshtoken=""
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, http://localhost:4321, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
dynamicRemoteAccessType="DISABLED"
[upc]
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
[connectionStatus]
minigraph="CONNECTED"
minigraph="ERROR_RETRYING"

20
api/package-lock.json generated
View File

@@ -75,6 +75,7 @@
"openid-client": "^5.6.5",
"p-iteration": "^1.1.8",
"p-retry": "^4.6.2",
"passport-custom": "^1.1.1",
"passport-http-header-strategy": "^1.1.0",
"pidusage": "^3.0.2",
"pino": "^9.1.0",
@@ -14492,6 +14493,17 @@
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-custom": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz",
"integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==",
"dependencies": {
"passport-strategy": "1.x.x"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/passport-http-header-strategy": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/passport-http-header-strategy/-/passport-http-header-strategy-1.1.0.tgz",
@@ -29188,6 +29200,14 @@
"utils-merge": "^1.0.1"
}
},
"passport-custom": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/passport-custom/-/passport-custom-1.1.1.tgz",
"integrity": "sha512-/2m7jUGxmCYvoqenLB9UrmkCgPt64h8ZtV+UtuQklZ/Tn1NpKBeOorCYkB/8lMRoiZ5hUrCoMmDtxCS/d38mlg==",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-http-header-strategy": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/passport-http-header-strategy/-/passport-http-header-strategy-1.1.0.tgz",

View File

@@ -126,6 +126,7 @@
"openid-client": "^5.6.5",
"p-iteration": "^1.1.8",
"p-retry": "^4.6.2",
"passport-custom": "^1.1.1",
"passport-http-header-strategy": "^1.1.0",
"pidusage": "^3.0.2",
"pino": "^9.1.0",

View File

@@ -69,12 +69,12 @@ export const configureFastifyCors =
if (typeof cookies === 'object') {
service.hasValidAuthCookie(cookies).then((isValid) => {
if (isValid) {
callback(null, { origin: true });
callback(null, { credentials: true, origin: true });
} else {
callback(null, { origin: isOriginAllowed });
callback(null, { credentials: true, origin: isOriginAllowed });
}
});
} else {
callback(null, { origin: isOriginAllowed });
callback(null, { credentials: true, origin: isOriginAllowed });
}
};

View File

@@ -11,10 +11,11 @@ import { Reflector } from '@nestjs/core';
import { GqlExecutionContext, type GqlContextType } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';
import { type Observable } from 'rxjs';
import { UserCookieStrategy } from './cookie.strategy';
@Injectable()
export class GraphqlAuthGuard
extends AuthGuard([ServerHeaderStrategy.key])
extends AuthGuard([ServerHeaderStrategy.key, UserCookieStrategy.key])
implements CanActivate
{
constructor(private readonly reflector: Reflector) {

View File

@@ -4,12 +4,14 @@ import { UsersModule } from '@app/unraid-api/users/users.module';
import { PassportModule } from '@nestjs/passport';
import { ServerHeaderStrategy } from '@app/unraid-api/auth/header.strategy';
import { CookieService, SESSION_COOKIE_CONFIG } from './cookie.service';
import { UserCookieStrategy } from './cookie.strategy';
@Module({
imports: [UsersModule, PassportModule],
providers: [
AuthService,
ServerHeaderStrategy,
UserCookieStrategy,
CookieService,
{ provide: SESSION_COOKIE_CONFIG, useValue: CookieService.defaultOpts() },
],

View File

@@ -1,17 +1,24 @@
import { type UserAccount } from '@app/graphql/generated/api/types';
import { UsersService } from '@app/unraid-api/users/users.service';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CookieService } from './cookie.service';
@Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
constructor(private usersService: UsersService, private cookieService: CookieService) {}
async validateUser(apiKey: string): Promise<UserAccount> {
const user = this.usersService.findOne(apiKey);
if (user) {
return user;
}
throw new UnauthorizedException('Invalid API key');
}
async validateCookies(cookies: object): Promise<UserAccount> {
if (await this.cookieService.hasValidAuthCookie(cookies)) {
return this.usersService.getSessionUser();
}
throw new UnauthorizedException('No user session found');
}
}

View File

@@ -0,0 +1,22 @@
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-custom';
import { Injectable, Logger } from '@nestjs/common';
import { AuthService } from './auth.service';
const strategyName = 'user-cookie';
@Injectable()
export class UserCookieStrategy extends PassportStrategy(Strategy, strategyName) {
static key = strategyName;
private readonly logger = new Logger(UserCookieStrategy.name);
constructor(
private readonly authService: AuthService,
) {
super();
}
public validate = async (req: any): Promise<any> => {
return this.authService.validateCookies(req.cookies);
};
}

View File

@@ -46,4 +46,19 @@ export class UsersService {
findOne(apiKey: string): UserAccount | null {
return this.apiKeyToUser(apiKey);
}
/**
* Returns a user object representing a session.
* Note: Does NOT perform validation.
*
* @returns a service account that represents the user session (i.e. a webgui user).
*/
getSessionUser(): UserAccount {
return {
id: '-1',
description: 'UPC service account',
name: 'upc',
roles: 'upc',
};
}
}

View File

@@ -38,11 +38,12 @@ watch(notifications, (newVal) => {
const fetchType = ref<"UNREAD" | "ARCHIVED">("UNREAD");
const setFetchType = (type: "UNREAD" | "ARCHIVED") => (fetchType.value = type);
const { unraidApiClient } = storeToRefs(useUnraidApiStore());
const { unraidApiClient: maybeApi } = storeToRefs(useUnraidApiStore());
watch(unraidApiClient, async (newVal) => {
if (newVal) {
const apiResponse = await newVal.query({
watch(maybeApi, async (apiClient) => {
if (apiClient) {
const apiResponse = await apiClient.query({
query: getNotifications,
variables: {
filter: {

View File

@@ -85,11 +85,13 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
// return; // @todo remove
unraidApiStatus.value = 'connecting';
const headers = { 'x-api-key': serverStore.apiKey };
// const headers = { 'x-api-key': serverStore.apiKey };
const headers = {};
const httpLink = createHttpLink({
uri: httpEndpoint.toString(),
headers,
credentials: "include",
});
const wsLink = new GraphQLWsLink(