diff --git a/backend/service-authentication/keys/jwtRS256.key b/backend/keys/jwtRS256.key similarity index 100% rename from backend/service-authentication/keys/jwtRS256.key rename to backend/keys/jwtRS256.key diff --git a/backend/service-authentication/keys/jwtRS256.key.pub b/backend/keys/jwtRS256.key.pub similarity index 100% rename from backend/service-authentication/keys/jwtRS256.key.pub rename to backend/keys/jwtRS256.key.pub diff --git a/backend/service-authentication/keys/jwtRS256.sh b/backend/keys/jwtRS256.sh similarity index 100% rename from backend/service-authentication/keys/jwtRS256.sh rename to backend/keys/jwtRS256.sh diff --git a/backend/service-authentication/auth.js b/backend/service-authentication/auth.js index 172742f..c47a3ef 100644 --- a/backend/service-authentication/auth.js +++ b/backend/service-authentication/auth.js @@ -21,8 +21,8 @@ module.exports.setSessionCookie = setSessionCookie; // de session. Si ce dernier n'existe pas, on renvoie // l'ID -1. function getUserId(session) { - if (typeof session.userId === 'undefined') return -1; - return session.userId; + if (typeof session.username === 'undefined') return -1; + return session.username; } module.exports.getUserId = getUserId; diff --git a/backend/service-authentication/config.js b/backend/service-authentication/config.js index 4e951d0..da5ca03 100644 --- a/backend/service-authentication/config.js +++ b/backend/service-authentication/config.js @@ -1,7 +1,7 @@ const config = { // paramètres de connexion à la base de données mongodbDatabase: 'chat', - mongodbHost: 'mongodb://mongodb-authentication:27017/', + mongodbHost: 'mongodb://127.0.0.1:27017/', charset: 'utf8', mongodbLogin: '', mongodbPassword: '', diff --git a/backend/service-authentication/server.js b/backend/service-authentication/server.js index a15ce7a..7f226dc 100644 --- a/backend/service-authentication/server.js +++ b/backend/service-authentication/server.js @@ -20,6 +20,7 @@ mongoConnect.connectToServer(function( err, client ) { const register = require('./register'); const queries = require('./mongodbQueries'); + queries.register('Server','admin'); queries.register('khai','test'); queries.register('wilfried','test'); queries.register('yuki','test'); diff --git a/backend/service-authentication/sessionJWT.js b/backend/service-authentication/sessionJWT.js index 90133d1..ebec454 100644 --- a/backend/service-authentication/sessionJWT.js +++ b/backend/service-authentication/sessionJWT.js @@ -1,11 +1,12 @@ const sessionJWT = require ('jsonwebtoken'); const fs = require ('fs'); + // renvoie un nouveau token JWT -function createSessionJWT (userId) { +function createSessionJWT (username) { // ci-dessous, on met en place le cookie de session JWT : // 1/ on recupere notre clef privee - const RSA_PRIVATE_KEY = fs.readFileSync('./keys/jwtRS256.key'); + const RSA_PRIVATE_KEY = fs.readFileSync('../keys/jwtRS256.key'); // 2/ on signe un token JWT. Le payload est l'identifiant de // l'utilisateur ainsi qu'une date d'expiration à mi-parcours : @@ -18,7 +19,7 @@ function createSessionJWT (userId) { // session. const jwtToken = sessionJWT.sign( { - userId: userId, + username: username, midExp: Math.floor(Date.now() / 1000) + 1800 // validité: 30mn }, RSA_PRIVATE_KEY, @@ -40,14 +41,15 @@ function createSessionCookie(req, res, payload) { // midExp, alors le cookie est encore valide et on peut le renvoyer. Sinon, // on doit recalculer un nouveau cookie. let jwtToken = ''; - if ((typeof payload.userId !== 'undefined') && + if ((typeof payload.username !== 'undefined') && (typeof payload.midExp !== 'undefined') && (Math.floor(Date.now() / 1000) <= payload.midExp)) { - jwtToken = req.cookies.SESSIONID; + jwtToken = req.headers.cookie; } else { - // on crée un nouveau cookie - jwtToken = createSessionJWT(payload.userId); + // on crée + // un nouveau cookie + jwtToken = createSessionJWT(payload.username); } // on renvoie le cookie au client @@ -63,14 +65,13 @@ module.exports.createSessionCookie = createSessionCookie; function decodeSessionCookie(req) { // si l'on n'a pas de cookie de session, on renvoie une session avec vide, // avec juste un userId à -1 - console.log(req.cookies); - if (typeof req.cookies.SESSIONID === 'undefined') { - return { userId: -1 }; - } - const sessionid = req.cookies.SESSIONID; + if (typeof req.headers.cookie === 'undefined') { + return { username: -1 }; + } + const sessionid = req.headers.cookie.replace('SESSIONID=',''); // on lit la clef publique - const RSA_PUBLIC_KEY = fs.readFileSync('./keys/jwtRS256.key.pub'); + const RSA_PUBLIC_KEY = fs.readFileSync('../keys/jwtRS256.key.pub'); // on récupère les données du cookie try { @@ -81,7 +82,7 @@ function decodeSessionCookie(req) { return token; } catch (err) { - return {userId: -1}; + return {username: err}; } } module.exports.decodeSessionCookie = decodeSessionCookie; diff --git a/backend/service-message/config.js b/backend/service-message/config.js index 612edc0..8b1fed7 100644 --- a/backend/service-message/config.js +++ b/backend/service-message/config.js @@ -1,6 +1,6 @@ const config = { mongodbDatabase: 'chat', - mongodbHost: 'mongodb://mongodb-message:27020/', + mongodbHost: 'mongodb://127.0.0.1:27020/', charset: 'utf8', mongodbLogin: '', mongodbPassword: '', diff --git a/backend/service-message/message.js b/backend/service-message/message.js deleted file mode 100644 index 05714bc..0000000 --- a/backend/service-message/message.js +++ /dev/null @@ -1,13 +0,0 @@ -// renvoie un message au format JSON. On a besoin de passer en paramètre -// res, la réponse que l'on envoie au client (Angular). Le paramètre -// data est un objet JavaScript. Globalement, cette fonction est -// équivalente au "echo json_encode(data);" que vous utilisiez en PHP -function sendMessage (res, data) { - res.json ({ status: 'ok', data: data }); -} - -function sendError (res, reason) { - res.json ({ status: 'error', data: {reason: reason }}); -} - -module.exports = { sendMessage, sendError }; diff --git a/backend/service-message/mongodb-message.js b/backend/service-message/mongodb-message.js index 5b5ff65..324d34d 100644 --- a/backend/service-message/mongodb-message.js +++ b/backend/service-message/mongodb-message.js @@ -1,6 +1,6 @@ const config = require('./config'); const mongoose = require( 'mongoose' ); -const url = config.mongodbHost; +const url = config.mongodbHost+config.mongodbDatabase; mongoose.connect(url,({useNewUrlParser: true, useUnifiedTopology: true})).then( function(){ console.log('mongodb-message connected '+mongoose.connection.readyState); @@ -8,7 +8,6 @@ mongoose.connect(url,({useNewUrlParser: true, useUnifiedTopology: true})).then( console.log('error : '+err); }); - const schemaMessage = mongoose.Schema({ username:{ type: String, @@ -26,7 +25,7 @@ const schemaMessage = mongoose.Schema({ type: String, required: true } -}); +},{ versionKey: false }); const messages = mongoose.model(config.mongodbMessages, schemaMessage); diff --git a/backend/service-message/server.js b/backend/service-message/server.js index 8e8508e..bb685f7 100644 --- a/backend/service-message/server.js +++ b/backend/service-message/server.js @@ -5,50 +5,59 @@ const cors = require ('cors'); const cookieParser = require('cookie-parser'); const auth = require ('./auth'); const bodyParser = require ('body-parser'); -const {sendError, sendMessage} = require ('./message'); const messages = require('./mongodb-message'); const app = express(); const server = http.createServer(app); -const io = new Server(server); -const port = process.env.PORT || 3000; +const io = new Server(server, { + cors: { + origin: "http://127.0.0.1:4200", + methods: ["GET", "POST"], + credentials: true + } +}); +const port = process.env.PORT || 3001; app.use(bodyParser.json()); app.use(cors({origin: 'http://127.0.0.1:4200', credentials: true})); app.use(cookieParser()); -io.use(function(socket, next){ - const session = auth.getSession(socket.request); - const getUsername = auth.getUsername(session); - if (getUsername === -1) { - //sendError(res, 'not authenticated'); - } - auth.setSessionCookie(socket.request, socket.request.res || {}, next); -}); app.get('/', (req, res) => { res.sendFile(__dirname + '/index.html'); }); io.on('connection',socket => { - let users = {} + + let users = {}; const session = auth.getSession(socket.request); const getUsername = auth.getUsername(session); - + if (getUsername === -1) { + socket.emit('error','not authenticated'); + } console.log(`${getUsername} joined the chat.`); - socket.broadcast.emit('general',`${getUsername} joined the chat.`); + socket.broadcast.emit('general',[{ + username: 'Server', + date: new Date(), + channel: 'general', + message: `${getUsername} joined the chat.` + }]); users[socket.id] = getUsername; - messages.find({},(err, res) => { + messages.find({}, {'_id':0},{sort: {'date':1}},(err, res) => { if(err) throw err; if(res.length > 0){ - const savedChat = res; - socket.emit('general',savedChat); + //console.log(res, res.length); + socket.emit('general',res); } + socket.emit('general',[{ + username: 'Server', + date: new Date(), + channel: 'general', + message: `${getUsername} joined the chat.` + }]); }); socket.on('general',function(data){ - socket.broadcast.emit('general',data); - const username = data.username; const date = Date.now(); const channel = 'general'; @@ -62,22 +71,20 @@ io.on('connection',socket => { } ]).then(function(){ console.log(data, "inserted"); + socket.broadcast.emit('general',[data]); + socket.emit('general',[data]); }).catch(function(error){ console.log("error",error); }); }); - socket.on('typing',(user)=>{ - socket.broadcast.emit('notifyTyping',user) - }) - socket.on("disconnect", function() { - console.log(`${socket.id} left the chat.`); + console.log(`${getUsername} left the chat.`); }); }); server.listen(port, () => { - console.log('listening on *:3000'); + console.log(`listening on *:${port}/`); }); \ No newline at end of file diff --git a/backend/service-message/sessionJWT.js b/backend/service-message/sessionJWT.js index 7372845..ebec454 100644 --- a/backend/service-message/sessionJWT.js +++ b/backend/service-message/sessionJWT.js @@ -1,11 +1,12 @@ const sessionJWT = require ('jsonwebtoken'); const fs = require ('fs'); + // renvoie un nouveau token JWT -function createSessionJWT (userId) { +function createSessionJWT (username) { // ci-dessous, on met en place le cookie de session JWT : // 1/ on recupere notre clef privee - const RSA_PRIVATE_KEY = fs.readFileSync('./keys/jwtRS256.key'); + const RSA_PRIVATE_KEY = fs.readFileSync('../keys/jwtRS256.key'); // 2/ on signe un token JWT. Le payload est l'identifiant de // l'utilisateur ainsi qu'une date d'expiration à mi-parcours : @@ -18,7 +19,7 @@ function createSessionJWT (userId) { // session. const jwtToken = sessionJWT.sign( { - userId: userId, + username: username, midExp: Math.floor(Date.now() / 1000) + 1800 // validité: 30mn }, RSA_PRIVATE_KEY, @@ -40,14 +41,15 @@ function createSessionCookie(req, res, payload) { // midExp, alors le cookie est encore valide et on peut le renvoyer. Sinon, // on doit recalculer un nouveau cookie. let jwtToken = ''; - if ((typeof payload.userId !== 'undefined') && + if ((typeof payload.username !== 'undefined') && (typeof payload.midExp !== 'undefined') && (Math.floor(Date.now() / 1000) <= payload.midExp)) { - jwtToken = req.cookies.SESSIONID; + jwtToken = req.headers.cookie; } else { - // on crée un nouveau cookie - jwtToken = createSessionJWT(payload.userId); + // on crée + // un nouveau cookie + jwtToken = createSessionJWT(payload.username); } // on renvoie le cookie au client @@ -63,13 +65,13 @@ module.exports.createSessionCookie = createSessionCookie; function decodeSessionCookie(req) { // si l'on n'a pas de cookie de session, on renvoie une session avec vide, // avec juste un userId à -1 - if (typeof req.cookies.SESSIONID === 'undefined') { - return { userId: -1 }; - } - const sessionid = req.cookies.SESSIONID; + if (typeof req.headers.cookie === 'undefined') { + return { username: -1 }; + } + const sessionid = req.headers.cookie.replace('SESSIONID=',''); // on lit la clef publique - const RSA_PUBLIC_KEY = fs.readFileSync('./keys/jwtRS256.key.pub'); + const RSA_PUBLIC_KEY = fs.readFileSync('../keys/jwtRS256.key.pub'); // on récupère les données du cookie try { @@ -80,7 +82,7 @@ function decodeSessionCookie(req) { return token; } catch (err) { - return {userId: -1}; + return {username: err}; } } module.exports.decodeSessionCookie = decodeSessionCookie; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f44d99e..adb6a9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,23 @@ "requires": { "@angular-devkit/core": "12.0.1", "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/build-angular": { @@ -87,6 +104,23 @@ "webpack-dev-server": "3.11.2", "webpack-merge": "5.7.3", "webpack-subresource-integrity": "1.5.2" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/build-optimizer": { @@ -108,6 +142,23 @@ "requires": { "@angular-devkit/architect": "0.1200.1", "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/core": { @@ -122,6 +173,23 @@ "magic-string": "0.25.7", "rxjs": "6.6.7", "source-map": "0.7.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular-devkit/schematics": { @@ -133,6 +201,23 @@ "@angular-devkit/core": "12.0.1", "ora": "5.4.0", "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular/animations": { @@ -5620,6 +5705,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5628,6 +5722,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -9896,17 +9996,17 @@ } }, "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.1.0.tgz", + "integrity": "sha512-gCFO5iHIbRPwznl6hAYuwNFld8W4S2shtSJIqG27ReWXo9IWrCyEICxUA+6vJHwSR/OakoenC4QsDxq50tzYmw==", "requires": { - "tslib": "^1.9.0" + "tslib": "~2.1.0" }, "dependencies": { "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" } } }, diff --git a/frontend/package.json b/frontend/package.json index 3d9e707..ad847c4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,8 @@ "@types/socket.io-client": "^3.0.0", "bootstrap": "^4.6.0", "jquery": "^3.6.0", - "rxjs": "~6.6.0", + "rxjs": "^7.1.0", + "socket.io-client": "^4.1.2", "tslib": "^2.1.0", "zone.js": "~0.11.4" }, diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index e9d2b65..d5c32ae 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -1,8 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import {AppComponent} from "./app.component"; import {LoginComponent} from "./login/login.component"; -import {PrivateComponent} from "./private/private.component"; +import {GeneralComponent} from "./general/general.component"; const routes: Routes = [ { @@ -10,8 +9,8 @@ const routes: Routes = [ component: LoginComponent, }, { - path: 'private', - component: PrivateComponent, + path: 'general', + component: GeneralComponent, } ]; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 15f4949..ce86a8a 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -2,24 +2,18 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import {HttpClientModule} from "@angular/common/http"; import {FormsModule, ReactiveFormsModule} from "@angular/forms"; - - import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LoginComponent } from './login/login.component'; import { GeneralComponent } from './general/general.component'; -import { PrivateComponent } from './private/private.component'; -import { NavbarComponent } from './navbar/navbar.component'; -import {CommonModule} from "@angular/common"; +import {CommonModule, DatePipe} from "@angular/common"; @NgModule({ declarations: [ AppComponent, LoginComponent, - GeneralComponent, - PrivateComponent, - NavbarComponent + GeneralComponent ], imports: [ BrowserModule, @@ -27,9 +21,9 @@ import {CommonModule} from "@angular/common"; FormsModule, CommonModule, ReactiveFormsModule, - AppRoutingModule + AppRoutingModule, ], - providers: [], + providers: [DatePipe], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/frontend/src/app/general/general.component.html b/frontend/src/app/general/general.component.html index 96d610a..21f47dc 100644 --- a/frontend/src/app/general/general.component.html +++ b/frontend/src/app/general/general.component.html @@ -1,52 +1,25 @@ -
navbar works!
diff --git a/frontend/src/app/navbar/navbar.component.scss b/frontend/src/app/navbar/navbar.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/app/navbar/navbar.component.spec.ts b/frontend/src/app/navbar/navbar.component.spec.ts deleted file mode 100644 index f8ccd6f..0000000 --- a/frontend/src/app/navbar/navbar.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { NavbarComponent } from './navbar.component'; - -describe('NavbarComponent', () => { - let component: NavbarComponent; - let fixture: ComponentFixture{{selectedUser?.name}}
-