added role permissions on actions and autologin
This commit is contained in:
parent
f103f0cb4f
commit
78ab9acb04
@ -57,6 +57,7 @@ function App() {
|
|||||||
|
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
|
localStorage.setItem('manualLogout', 'true');
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchArticles = () => {
|
const fetchArticles = () => {
|
||||||
@ -221,20 +222,28 @@ function App() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="user-info">
|
<div className="user-info">
|
||||||
<span>Welcome, {currentUser?.display_name || currentUser?.username}!</span>
|
<span>Welcome, {currentUser?.display_name || currentUser?.username}({currentUser?.role})</span>
|
||||||
<button className="logout-button" onClick={handleLogout}>Logout</button>
|
<button className="logout-button" onClick={handleLogout}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentView === 'list' ? (
|
{currentView === 'list' ? (
|
||||||
<>
|
<>
|
||||||
<SearchBar onSearch={handleSearch} />
|
<SearchBar onSearch={handleSearch} />
|
||||||
|
{(currentUser?.role === 'Admin' || currentUser?.role === 'Editor') && (
|
||||||
<button className='create-new-button' onClick={handleCreateNew}>
|
<button className='create-new-button' onClick={handleCreateNew}>
|
||||||
+ Create New Article
|
+ Create New Article
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<ArticleList articles={articles} onArticleClick={handleArticleClick} />
|
<ArticleList articles={articles} onArticleClick={handleArticleClick} />
|
||||||
</>
|
</>
|
||||||
) : currentView === 'detail' ? (
|
) : currentView === 'detail' ? (
|
||||||
<ArticleDetail article={selectedArticle} onBack={handleBack} onEdit={handleEdit} onDelete={handleDelete} />
|
<ArticleDetail
|
||||||
|
article={selectedArticle}
|
||||||
|
onBack={handleBack}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
currentUser={currentUser}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ArticleEditor article={selectedArticle} onSave={handleSave} onCancel={handleCancelEdit} />
|
<ArticleEditor article={selectedArticle} onSave={handleSave} onCancel={handleCancelEdit} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,22 +1,28 @@
|
|||||||
import './ArticleDetail.css';
|
import './ArticleDetail.css';
|
||||||
|
|
||||||
function ArticleDetail({ article, onBack, onEdit, onDelete }) {
|
function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
|
||||||
if (!article) {
|
if (!article) {
|
||||||
return <div>No article selected</div>
|
return <div>No article selected</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canEdit = currentUser?.role === 'Admin' || currentUser?.role === 'Editor';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='article-detail'>
|
<div className='article-detail'>
|
||||||
<div className='button-group'>
|
<div className='button-group'>
|
||||||
<button className='back-button' onClick={onBack}>
|
<button className='back-button' onClick={onBack}>
|
||||||
← Back
|
← Back
|
||||||
</button>
|
</button>
|
||||||
|
{canEdit && (
|
||||||
|
<>
|
||||||
<button className='edit-button' onClick={onEdit}>
|
<button className='edit-button' onClick={onEdit}>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button className='delete-button' onClick={onDelete}>
|
<button className='delete-button' onClick={onDelete}>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='article-header'>
|
<div className='article-header'>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [msalInstance, setMsalInstance] = useState(null);
|
const [msalInstance, setMsalInstance] = useState(null);
|
||||||
|
const [autoLoginAttempted, setAutoLoginAttempted] = useState (false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeMasal = async () => {
|
const initializeMasal = async () => {
|
||||||
@ -20,6 +21,14 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
initializeMasal();
|
initializeMasal();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const manualLogout = localStorage.getItem('manualLogout');
|
||||||
|
if (msalInstance && !autoLoginAttempted && manualLogout !== 'true') {
|
||||||
|
setAutoLoginAttempted(true);
|
||||||
|
handleMicrosoftLogin();
|
||||||
|
}
|
||||||
|
}, [msalInstance])
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError('');
|
||||||
@ -40,6 +49,7 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
localStorage.removeItem('manualLogout');
|
||||||
onLoginSuccess(data.user, data.token);
|
onLoginSuccess(data.user, data.token);
|
||||||
} else {
|
} else {
|
||||||
setError(data.error || 'Login failed');
|
setError(data.error || 'Login failed');
|
||||||
@ -72,13 +82,15 @@ function Login({ onLoginSuccess, onSwitchToRegister }) {
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accessToken: loginResponse.accessToken // Send access token instead of code
|
accessToken: loginResponse.accessToken,
|
||||||
|
idToken: loginResponse.idToken
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
localStorage.removeItem('manualLogout');
|
||||||
onLoginSuccess(data.user, data.token);
|
onLoginSuccess(data.user, data.token);
|
||||||
} else {
|
} else {
|
||||||
setError(data.error || 'Microsoft login failed');
|
setError(data.error || 'Microsoft login failed');
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
{"fd_cli_101_101_migrate_oauth_status":true,"report_hash":"8ee96e64b8a3f4c5139f4c95c579919721a060b0421ea476a0870b7403a8245e"}
|
{"fd_cli_101_101_migrate_oauth_status":true,"report_hash":"ac127cdfb4fd074868d43aef801f187105d36d777c033140b5e2dc170c0e87c8"}
|
||||||
|
|||||||
@ -2,22 +2,17 @@
|
|||||||
"lints": [
|
"lints": [
|
||||||
{
|
{
|
||||||
"severity": 1,
|
"severity": 1,
|
||||||
"value": "app\\scripts\\app.js::6: 'client' declared and assigned in different scopes. Possible asynchronous race condition.",
|
"value": "app\\scripts\\app.js::2: Expected rejection to be handled.",
|
||||||
"type": "custom-js-lint"
|
"type": "custom-js-lint"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"severity": 1,
|
"severity": 1,
|
||||||
"value": "app\\scripts\\app.js::21: Expected rejection to be handled.",
|
"value": "app\\scripts\\app.js::72: 'isExpanded' declared and assigned in different scopes. Possible asynchronous race condition.",
|
||||||
"type": "custom-js-lint"
|
"type": "custom-js-lint"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"severity": 1,
|
"severity": 1,
|
||||||
"value": "app\\scripts\\app.js::91: 'isExpanded' declared and assigned in different scopes. Possible asynchronous race condition.",
|
"value": "app\\scripts\\app.js::87: Expected rejection to be handled.",
|
||||||
"type": "custom-js-lint"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"severity": 1,
|
|
||||||
"value": "app\\scripts\\app.js::106: Expected rejection to be handled.",
|
|
||||||
"type": "custom-js-lint"
|
"type": "custom-js-lint"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -31,218 +26,48 @@
|
|||||||
"column": 0
|
"column": 0
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 91,
|
"line": 10,
|
||||||
"column": 3
|
"column": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 2,
|
"line": 2,
|
||||||
"column": 2
|
"column": 4
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 90,
|
"line": 9,
|
||||||
"column": 5
|
"column": 7
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 3,
|
"line": 3,
|
||||||
"column": 4
|
"column": 8
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 3,
|
"line": 3,
|
||||||
"column": 41
|
"column": 38
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 5,
|
"line": 5,
|
||||||
"column": 4
|
"column": 8
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 5,
|
"line": 7,
|
||||||
"column": 21
|
"column": 11
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 8,
|
"line": 6,
|
||||||
"column": 25
|
"column": 12
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 59,
|
"line": 6,
|
||||||
"column": 13
|
"column": 52
|
||||||
}
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"start": {
|
|
||||||
"line": 62,
|
|
||||||
"column": 6
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 62,
|
|
||||||
"column": 64
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"start": {
|
|
||||||
"line": 65,
|
|
||||||
"column": 21
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 65,
|
|
||||||
"column": 57
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"start": {
|
|
||||||
"line": 66,
|
|
||||||
"column": 21
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 66,
|
|
||||||
"column": 64
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"start": {
|
|
||||||
"line": 67,
|
|
||||||
"column": 22
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 67,
|
|
||||||
"column": 66
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"start": {
|
|
||||||
"line": 68,
|
|
||||||
"column": 24
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 68,
|
|
||||||
"column": 64
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"10": {
|
|
||||||
"start": {
|
|
||||||
"line": 69,
|
|
||||||
"column": 23
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 69,
|
|
||||||
"column": 28
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"11": {
|
|
||||||
"start": {
|
|
||||||
"line": 71,
|
|
||||||
"column": 6
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 83,
|
|
||||||
"column": 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"12": {
|
|
||||||
"start": {
|
|
||||||
"line": 72,
|
|
||||||
"column": 8
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 72,
|
|
||||||
"column": 33
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"13": {
|
|
||||||
"start": {
|
|
||||||
"line": 74,
|
|
||||||
"column": 8
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 82,
|
|
||||||
"column": 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"14": {
|
|
||||||
"start": {
|
|
||||||
"line": 75,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 75,
|
|
||||||
"column": 39
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"15": {
|
|
||||||
"start": {
|
|
||||||
"line": 76,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 76,
|
|
||||||
"column": 42
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"16": {
|
|
||||||
"start": {
|
|
||||||
"line": 77,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 77,
|
|
||||||
"column": 38
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"17": {
|
|
||||||
"start": {
|
|
||||||
"line": 79,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 79,
|
|
||||||
"column": 39
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"18": {
|
|
||||||
"start": {
|
|
||||||
"line": 80,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 80,
|
|
||||||
"column": 41
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"19": {
|
|
||||||
"start": {
|
|
||||||
"line": 81,
|
|
||||||
"column": 10
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 81,
|
|
||||||
"column": 38
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"20": {
|
|
||||||
"start": {
|
|
||||||
"line": 87,
|
|
||||||
"column": 4
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 89,
|
|
||||||
"column": 7
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"21": {
|
|
||||||
"start": {
|
|
||||||
"line": 88,
|
|
||||||
"column": 6
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 88,
|
|
||||||
"column": 40
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -265,7 +90,7 @@
|
|||||||
"column": 58
|
"column": 58
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 91,
|
"line": 10,
|
||||||
"column": 1
|
"column": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -276,201 +101,92 @@
|
|||||||
"decl": {
|
"decl": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 2,
|
"line": 2,
|
||||||
"column": 25
|
"column": 27
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 2,
|
"line": 2,
|
||||||
"column": 26
|
"column": 28
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 2,
|
"line": 2,
|
||||||
"column": 43
|
"column": 45
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 90,
|
"line": 9,
|
||||||
"column": 3
|
"column": 5
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"line": 2
|
"line": 2
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"name": "injectKBDrawer",
|
"name": "(anonymous_2)",
|
||||||
"decl": {
|
"decl": {
|
||||||
"start": {
|
"start": {
|
||||||
"line": 7,
|
"line": 5,
|
||||||
"column": 13
|
"column": 75
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"column": 76
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loc": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"column": 86
|
||||||
},
|
},
|
||||||
"end": {
|
"end": {
|
||||||
"line": 7,
|
"line": 7,
|
||||||
"column": 27
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loc": {
|
|
||||||
"start": {
|
|
||||||
"line": 7,
|
|
||||||
"column": 30
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 84,
|
|
||||||
"column": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line": 7
|
|
||||||
},
|
|
||||||
"3": {
|
|
||||||
"name": "(anonymous_3)",
|
|
||||||
"decl": {
|
|
||||||
"start": {
|
|
||||||
"line": 71,
|
|
||||||
"column": 39
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 71,
|
|
||||||
"column": 40
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loc": {
|
|
||||||
"start": {
|
|
||||||
"line": 71,
|
|
||||||
"column": 51
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 83,
|
|
||||||
"column": 7
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line": 71
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"name": "(anonymous_4)",
|
|
||||||
"decl": {
|
|
||||||
"start": {
|
|
||||||
"line": 87,
|
|
||||||
"column": 35
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 87,
|
|
||||||
"column": 36
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"loc": {
|
|
||||||
"start": {
|
|
||||||
"line": 87,
|
|
||||||
"column": 51
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 89,
|
|
||||||
"column": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"line": 87
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"branchMap": {
|
|
||||||
"0": {
|
|
||||||
"loc": {
|
|
||||||
"start": {
|
|
||||||
"line": 74,
|
|
||||||
"column": 8
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 82,
|
|
||||||
"column": 9
|
"column": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "if",
|
"line": 5
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"start": {
|
|
||||||
"line": 74,
|
|
||||||
"column": 8
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 82,
|
|
||||||
"column": 9
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"start": {
|
|
||||||
"line": 78,
|
|
||||||
"column": 15
|
|
||||||
},
|
|
||||||
"end": {
|
|
||||||
"line": 82,
|
|
||||||
"column": 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"line": 74
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"branchMap": {},
|
||||||
"s": {
|
"s": {
|
||||||
"0": 8,
|
"0": 12,
|
||||||
"1": 8,
|
"1": 12,
|
||||||
"2": 7,
|
"2": 6,
|
||||||
"3": 7,
|
"3": 6,
|
||||||
"4": 7,
|
"4": 1
|
||||||
"5": 7,
|
|
||||||
"6": 7,
|
|
||||||
"7": 7,
|
|
||||||
"8": 7,
|
|
||||||
"9": 7,
|
|
||||||
"10": 7,
|
|
||||||
"11": 7,
|
|
||||||
"12": 0,
|
|
||||||
"13": 0,
|
|
||||||
"14": 0,
|
|
||||||
"15": 0,
|
|
||||||
"16": 0,
|
|
||||||
"17": 0,
|
|
||||||
"18": 0,
|
|
||||||
"19": 0,
|
|
||||||
"20": 7,
|
|
||||||
"21": 7
|
|
||||||
},
|
},
|
||||||
"f": {
|
"f": {
|
||||||
"0": 8,
|
"0": 12,
|
||||||
"1": 7,
|
"1": 6,
|
||||||
"2": 7,
|
"2": 1
|
||||||
"3": 0,
|
|
||||||
"4": 7
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
"0": [
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
"b": {},
|
||||||
"_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9",
|
"_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9",
|
||||||
"hash": "62a764b53a2397355729862d774423d12cc8d951"
|
"hash": "18566891b473df9f163282f1310c6fc8160a5fe6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"coverageSummary": {
|
"coverageSummary": {
|
||||||
"lines": {
|
"lines": {
|
||||||
"total": 22,
|
"total": 5,
|
||||||
"covered": 14,
|
"covered": 5,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
"pct": 63.63
|
"pct": 100
|
||||||
},
|
},
|
||||||
"statements": {
|
"statements": {
|
||||||
"total": 22,
|
"total": 5,
|
||||||
"covered": 14,
|
"covered": 5,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
"pct": 63.63
|
"pct": 100
|
||||||
},
|
},
|
||||||
"functions": {
|
"functions": {
|
||||||
"total": 5,
|
"total": 3,
|
||||||
"covered": 4,
|
"covered": 3,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
"pct": 80
|
"pct": 100
|
||||||
},
|
},
|
||||||
"branches": {
|
"branches": {
|
||||||
"total": 2,
|
"total": 0,
|
||||||
"covered": 0,
|
"covered": 0,
|
||||||
"skipped": 0,
|
"skipped": 0,
|
||||||
"pct": 0
|
"pct": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,9 +73,32 @@ function authenticateToken(req, res, next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to check if user has required role
|
||||||
|
* @param {Array} allowedRoles - Array of roles that can acces the route
|
||||||
|
*/
|
||||||
|
function authorizeRoles(...allowedRoles) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({error: 'Authentication requried'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowedRoles.includes(req.user.role)) {
|
||||||
|
return res.status(403).json({
|
||||||
|
error: 'Insufficient permissions',
|
||||||
|
required: allowedRoles,
|
||||||
|
current: req.user.role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
generateToken,
|
generateToken,
|
||||||
verifyToken,
|
verifyToken,
|
||||||
authenticateToken,
|
authenticateToken,
|
||||||
|
authorizeRoles,
|
||||||
JWT_SECRET
|
JWT_SECRET
|
||||||
};
|
};
|
||||||
@ -12,11 +12,12 @@ const {
|
|||||||
getUserByUsername,
|
getUserByUsername,
|
||||||
getUserByEmail
|
getUserByEmail
|
||||||
} = require('./db');
|
} = require('./db');
|
||||||
const { generateToken, authenticateToken } = require('./auth');
|
const { generateToken, authenticateToken, authorizeRoles } = require('./auth');
|
||||||
const app = express();
|
const app = express();
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const msal = require('@azure/msal-node');
|
const msal = require('@azure/msal-node');
|
||||||
const entraConfig = require('./entraConfig');
|
const entraConfig = require('./entraConfig');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
const PORT = 9000;
|
const PORT = 9000;
|
||||||
|
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
@ -55,7 +56,7 @@ initDb().then(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/articles', authenticateToken, (req, res) => {
|
app.post('/api/articles', authenticateToken, authorizeRoles('Admin', 'Editor'), (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { title, content } = req.body;
|
const { title, content } = req.body;
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ initDb().then(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/articles/:ka_number', authenticateToken, (req, res) => {
|
app.put('/api/articles/:ka_number', authenticateToken, authorizeRoles('Admin', 'Editor'), (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { title, content } = req.body;
|
const { title, content } = req.body;
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ initDb().then(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/api/articles/:ka_number', authenticateToken, (req, res) => {
|
app.delete('/api/articles/:ka_number', authenticateToken, authorizeRoles('Admin', 'Editor'), (req, res) => {
|
||||||
try {
|
try {
|
||||||
deleteArticle(req.params.ka_number);
|
deleteArticle(req.params.ka_number);
|
||||||
return res.status(200).json({'message': 'Successfully deleted article'});
|
return res.status(200).json({'message': 'Successfully deleted article'});
|
||||||
@ -149,7 +150,7 @@ initDb().then(() => {
|
|||||||
email: newUser.email,
|
email: newUser.email,
|
||||||
display_name: newUser.display_name,
|
display_name: newUser.display_name,
|
||||||
auth_provider: newUser.auth_provider,
|
auth_provider: newUser.auth_provider,
|
||||||
role: user.role,
|
role: newUser.role,
|
||||||
created_at: newUser.created_at
|
created_at: newUser.created_at
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
@ -202,7 +203,7 @@ initDb().then(() => {
|
|||||||
|
|
||||||
app.post('/api/auth/microsoft', async (req, res) => {
|
app.post('/api/auth/microsoft', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { accessToken } = req.body;
|
const { accessToken, idToken } = req.body;
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
return res.status(400).json({error: 'Access token required'});
|
return res.status(400).json({error: 'Access token required'});
|
||||||
@ -219,8 +220,8 @@ initDb().then(() => {
|
|||||||
return res.status(401).json({error: 'Invalid Microsoft token'});
|
return res.status(401).json({error: 'Invalid Microsoft token'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoded = jwt.decode(accessToken);
|
const decoded = jwt.decode(idToken);
|
||||||
const roles = decoded.roles || {};
|
const roles = decoded.roles || [];
|
||||||
let userRole = 'User';
|
let userRole = 'User';
|
||||||
|
|
||||||
if (roles.includes('Admin')) userRole = 'Admin';
|
if (roles.includes('Admin')) userRole = 'Admin';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user