Handle JWT tokens

This commit is contained in:
Filip Gralinski 2020-12-09 21:55:31 +01:00
parent a0577ba249
commit ceef7ae5ac
9 changed files with 1551 additions and 1 deletions

View File

@ -168,6 +168,9 @@ instance Yesod App where
isAuthorized (ChallengeHowToR _) _ = regularAuthorization
isAuthorized (ChallengeReadmeR _) _ = regularAuthorization
isAuthorized (ChallengeAllSubmissionsR _) _ = regularAuthorization
isAuthorized (ChallengeMySubmissionsJsonR _) _ = regularAuthorization
isAuthorized (ChallengeGraphDataR _) _ = regularAuthorization
isAuthorized (ChallengeDiscussionR _) _ = regularAuthorization
isAuthorized (ChallengeDiscussionFeedR _) _ = regularAuthorization

View File

@ -20,6 +20,13 @@ import Handler.Dashboard
import Handler.Common
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 Text.Blaze
@ -649,6 +656,23 @@ submissionForm defaultUrl defBranch defaultGitAnnexRemote = renderBootstrap3 Boo
<*> areq textField (bfs MsgSubmissionBranch) defBranch
<*> 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 name = do
userId <- requireAuthId

View File

@ -18,6 +18,9 @@ import Yesod.Default.Config2 (applyEnvValue, configSettingsYml)
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
widgetFileReload)
import qualified Jose.Jwk as JWK
import Data.Aeson
data RepoScheme = SelfHosted | Branches
deriving (Eq, Show)
@ -92,6 +95,7 @@ data AppSettings = AppSettings
, appServerSSHPublicKey :: Maybe Text
-- ^ Are challenges, submission, etc. visible without logging in
, appIsPublic :: Bool
, appJSONWebKey :: Maybe JWK.Jwk
}
instance FromJSON AppSettings where
@ -137,6 +141,8 @@ instance FromJSON AppSettings where
appIsPublic <- o .:? "is-public" .!= False
appJSONWebKey <- o .:? "json-web-key"
return AppSettings {..}
-- | Settings for 'widgetFile', such as which template languages to support and

View File

@ -12,6 +12,7 @@
/list-challenges ListChallengesR GET
/api/list-challenges ListChallengesJsonR GET
/api/leaderboard/#Text LeaderboardJsonR GET
/api/challenge-my-submissions/#Text ChallengeMySubmissionsJsonR GET
/list-archived-challenges ListArchivedChallengesR GET
/challenge-image/#ChallengeId ChallengeImageR GET

View File

@ -41,4 +41,25 @@ admin-user: "_env:ADMINUSER:"
admin-password: "_env:ADMINPASS:"
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

View File

@ -152,6 +152,8 @@ library
, Glob
, req
, wai-cors
, word8
, jose-jwt
executable gonito
if flag(library-only)

View File

@ -4,5 +4,18 @@ flags:
dev: false
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

1426
static/js/keycloak.js Normal file

File diff suppressed because it is too large Load Diff

View 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>