Handle JWT tokens
This commit is contained in:
parent
a0577ba249
commit
ceef7ae5ac
@ -168,6 +168,9 @@ instance Yesod App where
|
|||||||
isAuthorized (ChallengeHowToR _) _ = regularAuthorization
|
isAuthorized (ChallengeHowToR _) _ = regularAuthorization
|
||||||
isAuthorized (ChallengeReadmeR _) _ = regularAuthorization
|
isAuthorized (ChallengeReadmeR _) _ = regularAuthorization
|
||||||
isAuthorized (ChallengeAllSubmissionsR _) _ = regularAuthorization
|
isAuthorized (ChallengeAllSubmissionsR _) _ = regularAuthorization
|
||||||
|
|
||||||
|
isAuthorized (ChallengeMySubmissionsJsonR _) _ = regularAuthorization
|
||||||
|
|
||||||
isAuthorized (ChallengeGraphDataR _) _ = regularAuthorization
|
isAuthorized (ChallengeGraphDataR _) _ = regularAuthorization
|
||||||
isAuthorized (ChallengeDiscussionR _) _ = regularAuthorization
|
isAuthorized (ChallengeDiscussionR _) _ = regularAuthorization
|
||||||
isAuthorized (ChallengeDiscussionFeedR _) _ = regularAuthorization
|
isAuthorized (ChallengeDiscussionFeedR _) _ = regularAuthorization
|
||||||
|
@ -20,6 +20,13 @@ import Handler.Dashboard
|
|||||||
import Handler.Common
|
import Handler.Common
|
||||||
import Handler.Evaluate
|
import Handler.Evaluate
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import Data.Word8 (isSpace, toLower)
|
||||||
|
import Network.Wai (Request, requestHeaders)
|
||||||
|
import qualified Jose.Jwt as JWT
|
||||||
|
import qualified Jose.Jwa as JWA
|
||||||
|
import qualified Jose.Jwk as JWK
|
||||||
|
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
|
|
||||||
import Text.Blaze
|
import Text.Blaze
|
||||||
@ -649,6 +656,23 @@ submissionForm defaultUrl defBranch defaultGitAnnexRemote = renderBootstrap3 Boo
|
|||||||
<*> areq textField (bfs MsgSubmissionBranch) defBranch
|
<*> areq textField (bfs MsgSubmissionBranch) defBranch
|
||||||
<*> aopt textField (bfs MsgSubmissionGitAnnexRemote) (Just defaultGitAnnexRemote))
|
<*> aopt textField (bfs MsgSubmissionGitAnnexRemote) (Just defaultGitAnnexRemote))
|
||||||
|
|
||||||
|
getChallengeMySubmissionsJsonR :: Text -> Handler Value
|
||||||
|
getChallengeMySubmissionsJsonR name = do
|
||||||
|
req <- waiRequest
|
||||||
|
let mToken = case lookup "Authorization" (Network.Wai.requestHeaders req) of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just authHead -> case BS.break isSpace authHead of
|
||||||
|
(strategy, token)
|
||||||
|
| BS.map Data.Word8.toLower strategy == "bearer" -> (Just $ BS.filter (/= 32) token)
|
||||||
|
| otherwise -> Nothing
|
||||||
|
mUserEnt <- maybeAuth
|
||||||
|
|
||||||
|
app <- getYesod
|
||||||
|
let jwk = fromJust $ appJSONWebKey $ appSettings app
|
||||||
|
|
||||||
|
dtoken <- liftIO $ JWT.decode [jwk] (Just (JWT.JwsEncoding JWA.RS256)) $ fromJust mToken
|
||||||
|
return $ array [show dtoken]
|
||||||
|
|
||||||
getChallengeMySubmissionsR :: Text -> Handler Html
|
getChallengeMySubmissionsR :: Text -> Handler Html
|
||||||
getChallengeMySubmissionsR name = do
|
getChallengeMySubmissionsR name = do
|
||||||
userId <- requireAuthId
|
userId <- requireAuthId
|
||||||
|
@ -18,6 +18,9 @@ import Yesod.Default.Config2 (applyEnvValue, configSettingsYml)
|
|||||||
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
|
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
|
||||||
widgetFileReload)
|
widgetFileReload)
|
||||||
|
|
||||||
|
import qualified Jose.Jwk as JWK
|
||||||
|
import Data.Aeson
|
||||||
|
|
||||||
data RepoScheme = SelfHosted | Branches
|
data RepoScheme = SelfHosted | Branches
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
@ -92,6 +95,7 @@ data AppSettings = AppSettings
|
|||||||
, appServerSSHPublicKey :: Maybe Text
|
, appServerSSHPublicKey :: Maybe Text
|
||||||
-- ^ Are challenges, submission, etc. visible without logging in
|
-- ^ Are challenges, submission, etc. visible without logging in
|
||||||
, appIsPublic :: Bool
|
, appIsPublic :: Bool
|
||||||
|
, appJSONWebKey :: Maybe JWK.Jwk
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON AppSettings where
|
instance FromJSON AppSettings where
|
||||||
@ -137,6 +141,8 @@ instance FromJSON AppSettings where
|
|||||||
|
|
||||||
appIsPublic <- o .:? "is-public" .!= False
|
appIsPublic <- o .:? "is-public" .!= False
|
||||||
|
|
||||||
|
appJSONWebKey <- o .:? "json-web-key"
|
||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|
||||||
-- | Settings for 'widgetFile', such as which template languages to support and
|
-- | Settings for 'widgetFile', such as which template languages to support and
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
/list-challenges ListChallengesR GET
|
/list-challenges ListChallengesR GET
|
||||||
/api/list-challenges ListChallengesJsonR GET
|
/api/list-challenges ListChallengesJsonR GET
|
||||||
/api/leaderboard/#Text LeaderboardJsonR GET
|
/api/leaderboard/#Text LeaderboardJsonR GET
|
||||||
|
/api/challenge-my-submissions/#Text ChallengeMySubmissionsJsonR GET
|
||||||
/list-archived-challenges ListArchivedChallengesR GET
|
/list-archived-challenges ListArchivedChallengesR GET
|
||||||
/challenge-image/#ChallengeId ChallengeImageR GET
|
/challenge-image/#ChallengeId ChallengeImageR GET
|
||||||
|
|
||||||
|
@ -41,4 +41,25 @@ admin-user: "_env:ADMINUSER:"
|
|||||||
admin-password: "_env:ADMINPASS:"
|
admin-password: "_env:ADMINPASS:"
|
||||||
location: "_env:LOCATION:"
|
location: "_env:LOCATION:"
|
||||||
|
|
||||||
|
# If set, the key given, in the JWK format, will be used to verify and
|
||||||
|
# trust JWT tokens sent by the client as Authorization/Bearer.
|
||||||
|
# The JWT token will be checked first for a given request, if not provided
|
||||||
|
# the standard auth procedure will used.
|
||||||
|
#
|
||||||
|
# If unset, the Authorization will NOT be checked at all (only the standard
|
||||||
|
# auth procedure will be applied).
|
||||||
|
#
|
||||||
|
# A JWK key is something like:
|
||||||
|
#
|
||||||
|
# {"kty":"RSA", alg:"RS256", "use":"sig", "kid":"h01jmt_bD-1Di8i_GYbEV2a4NxhptzySHO-R8VuNHVA", "e":"AQAB", "n": "qG1elE6KPW3BYMxNpgK73MoksvbrUSfpRY4z9hU5iMsJREyD5Ar6XpjM1xAr6G7xglnOoumPC9o6FqhDHihm6QdJ5s5MA9ZyGkbi--kvy9Qc2d_VIGU-UR4vwyk3hAwXOFLhoknpQrJBJmMQvGFdas1Yr-m9EIWwT1zN7neHZkRUYZSVyQw_XghtMIWAUsLnhr6mM7nstHLafgxe5Qamzuc4K5EC_qipFXu4ugYkMDnaknlhkT43m7tcduVDnv5GV_4dBesF7FRII8tgUQWyw3Ty_FIoq43SInUPU_9cxA-qPGQz5C50th2aJl1z1snpLWS_1Zfsa8lnFsMj8_oh6w"}
|
||||||
|
#
|
||||||
|
# If you use Keycloak, it can be retrived via:
|
||||||
|
#
|
||||||
|
# https://<HOST>/auth/realms/<REALM>/protocol/openid-connect/certs
|
||||||
|
#
|
||||||
|
# (key/0 element).
|
||||||
|
#
|
||||||
|
# Note: at the moment, only RS256 is handled.
|
||||||
|
json-web-key: "_env:JSON_WEB_KEY"
|
||||||
|
|
||||||
#analytics: UA-YOURCODE
|
#analytics: UA-YOURCODE
|
||||||
|
@ -152,6 +152,8 @@ library
|
|||||||
, Glob
|
, Glob
|
||||||
, req
|
, req
|
||||||
, wai-cors
|
, wai-cors
|
||||||
|
, word8
|
||||||
|
, jose-jwt
|
||||||
|
|
||||||
executable gonito
|
executable gonito
|
||||||
if flag(library-only)
|
if flag(library-only)
|
||||||
|
15
stack.yaml
15
stack.yaml
@ -4,5 +4,18 @@ flags:
|
|||||||
dev: false
|
dev: false
|
||||||
packages:
|
packages:
|
||||||
- '.'
|
- '.'
|
||||||
extra-deps: [../geval,wai-handler-fastcgi-3.0.0.2,murmur3-1.0.3,random-strings-0.1.1.0,naturalcomp-0.0.3,Munkres-0.1,Chart-1.9.1,Chart-cairo-1.9.1,multiset-0.3.4.1,pwstore-fast-2.4.4,yesod-table-2.0.3,esqueleto-3.0.0,'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810']
|
extra-deps:
|
||||||
|
- ../geval
|
||||||
|
- wai-handler-fastcgi-3.0.0.2
|
||||||
|
- murmur3-1.0.3
|
||||||
|
- random-strings-0.1.1.0
|
||||||
|
- naturalcomp-0.0.3
|
||||||
|
- Munkres-0.1
|
||||||
|
- Chart-1.9.1
|
||||||
|
- Chart-cairo-1.9.1
|
||||||
|
- multiset-0.3.4.1
|
||||||
|
- pwstore-fast-2.4.4
|
||||||
|
- yesod-table-2.0.3
|
||||||
|
- esqueleto-3.0.0
|
||||||
|
- 'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810'
|
||||||
resolver: lts-12.26
|
resolver: lts-12.26
|
||||||
|
1426
static/js/keycloak.js
Normal file
1426
static/js/keycloak.js
Normal file
File diff suppressed because it is too large
Load Diff
54
static/test-gonito-as-backend.html
Normal file
54
static/test-gonito-as-backend.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="/static/js/keycloak.js"></script>
|
||||||
|
<script>
|
||||||
|
var keycloak;
|
||||||
|
function initKeycloak() {
|
||||||
|
keycloak = new Keycloak({
|
||||||
|
url: 'http://127.0.0.1:8080/auth',
|
||||||
|
realm: 'master',
|
||||||
|
clientId: 'myapp',
|
||||||
|
"enable-cors": true
|
||||||
|
})
|
||||||
|
keycloak.init({
|
||||||
|
onLoad: 'login-required'
|
||||||
|
}).then(function(authenticated) {
|
||||||
|
// alert(authenticated ? 'authenticated' : 'not authenticated');
|
||||||
|
}).catch(function() {
|
||||||
|
alert('failed to initialize');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadData = function () {
|
||||||
|
|
||||||
|
var url = 'http://127.0.0.1:3000/api/challenge-my-submissions/retroc2';
|
||||||
|
|
||||||
|
var req = new XMLHttpRequest();
|
||||||
|
req.open('GET', url, true);
|
||||||
|
req.setRequestHeader('Accept', 'application/json');
|
||||||
|
req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
|
||||||
|
|
||||||
|
req.onreadystatechange = function () {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status == 200) {
|
||||||
|
alert(req.response);
|
||||||
|
} else if (req.status == 403) {
|
||||||
|
alert('Forbidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="initKeycloak()">
|
||||||
|
<h1>This is a simple web page to test Gonito as a backend with authorization by JWT tokens.</h1>
|
||||||
|
|
||||||
|
<p><button onclick="loadData()">Test</button></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user