@@ -8,6 +8,10 @@ import Data.Set qualified as Set
88import Data.Text (Text )
99import Data.Time (DiffTime )
1010import Database.Redis qualified as R
11+ import GHC.Stack (HasCallStack )
12+ import Network.URI qualified as URI
13+ import Network.Wai.Handler.Warp qualified as Warp
14+ import Servant
1115import Share.JWT qualified as JWT
1216import Share.OAuth.API (ServiceProviderAPI )
1317import Share.OAuth.IdentityProvider.Share qualified as Share
@@ -18,10 +22,6 @@ import Share.OAuth.ServiceProvider qualified as Auth
1822import Share.OAuth.Session (AuthCheckCtx , AuthenticatedUserId , MaybeAuthenticatedUserId , addAuthCheckCtx )
1923import Share.OAuth.Types (OAuthClientId (.. ), OAuthClientSecret (OAuthClientSecret ), RedirectReceiverErr , UserId )
2024import Share.Utils.Servant.Cookies qualified as Cookies
21- import GHC.Stack (HasCallStack )
22- import Network.URI qualified as URI
23- import Network.Wai.Handler.Warp qualified as Warp
24- import Servant
2525import UnliftIO
2626
2727-- | An example application endpoint which is optionally authenticated.
@@ -44,30 +44,30 @@ type MyAPI =
4444 :<|> " error" :> ErrorEndpoint
4545
4646-- | A handler which checks if the user is authenticated.
47- mayAuthedEndpoint :: MonadIO m => Maybe UserId -> m String
47+ mayAuthedEndpoint :: ( MonadIO m ) => Maybe UserId -> m String
4848mayAuthedEndpoint mayCallerUserId = do
4949 case mayCallerUserId of
5050 Nothing -> pure " no user"
5151 Just userId -> do
5252 pure $ " Hello, " <> show userId
5353
5454-- | A handler which requires an authenticated user.
55- authedEndpoint :: MonadIO m => UserId -> m String
55+ authedEndpoint :: ( MonadIO m ) => UserId -> m String
5656authedEndpoint callerUserId = do
5757 pure $ " Hello, " <> show callerUserId
5858
5959-- | A handler which displays errors from the OAuth2 flow.
60- errorEndpoint :: Applicative m => Maybe String -> m String
60+ errorEndpoint :: ( Applicative m ) => Maybe String -> m String
6161errorEndpoint err = do
6262 pure $ fromMaybe " no error" err
6363
6464-- | A helper function for constructing URIs from constant strings.
65- unsafeURI :: HasCallStack => String -> URI. URI
65+ unsafeURI :: ( HasCallStack ) => String -> URI. URI
6666unsafeURI = fromJust . URI. parseURI
6767
6868-- | A session callback which redirects the user to either an error page
6969-- or the authed handler endpoint depending on whether the oauth2 login succeeds.
70- mySessionCallback :: Applicative m => Either RedirectReceiverErr SessionCallbackData -> m URI
70+ mySessionCallback :: ( Applicative m ) => Either RedirectReceiverErr SessionCallbackData -> m URI
7171mySessionCallback (Left err) = pure . fromJust . URI. parseURI $ " http://cloud:3030/error?error=" <> show err
7272mySessionCallback (Right _session) = pure $ unsafeURI " http://cloud:3030/authed"
7373
@@ -78,7 +78,12 @@ main = do
7878 redisConn <- R. checkedConnect R. defaultConnectInfo
7979 putStrLn " booting up"
8080
81- Warp. run 3030 $ serveWithContext (Proxy @ MyAPI ) ctx (myServer redisConn)
81+ jwtSettings <- case JWT. defaultJWTSettings signingKey (Just legacyKey) rotatedKeys acceptedAudiences issuer of
82+ Left cryptoError -> throwIO cryptoError
83+ Right jwtS -> do
84+ pure jwtS
85+
86+ Warp. run 3030 $ serveWithContext (Proxy @ MyAPI ) (ctx jwtSettings) (myServer redisConn jwtSettings)
8287 putStrLn " exiting"
8388 pure ()
8489 where
@@ -87,18 +92,18 @@ main = do
8792 apiProxy :: Proxy MyAPI
8893 apiProxy = Proxy
8994 -- The api context required by servant-auth
90- appCtx :: (Context '[Cookies. CookieSettings , JWT. JWTSettings ])
91- appCtx = cookieSettings :. jwtSettings :. EmptyContext
95+ appCtx :: JWT. JWTSettings -> (Context '[Cookies. CookieSettings , JWT. JWTSettings ])
96+ appCtx jwtSettings = cookieSettings :. jwtSettings :. EmptyContext
9297 sessionCookieKey :: Text
9398 sessionCookieKey = " session"
94- ctx :: Context (AuthCheckCtx .++ '[Cookies. CookieSettings , JWT. JWTSettings ])
95- ctx = addAuthCheckCtx cookieSettings jwtSettings " session" appCtx
96- serviceProviderEndpoints :: ServerT ServiceProviderAPI R. Redis
97- serviceProviderEndpoints = Auth. serviceProviderServer Share. localShareIdentityProvider spConfig mySessionCallback
98- myServer :: R. Connection -> Server MyAPI
99- myServer conn =
99+ ctx :: JWT. JWTSettings -> Context (AuthCheckCtx .++ '[Cookies. CookieSettings , JWT. JWTSettings ])
100+ ctx jwtSettings = addAuthCheckCtx cookieSettings jwtSettings " session" ( appCtx jwtSettings)
101+ serviceProviderEndpoints :: JWT. JWTSettings -> ServerT ServiceProviderAPI R. Redis
102+ serviceProviderEndpoints jwtSettings = Auth. serviceProviderServer Share. localShareIdentityProvider ( spConfig jwtSettings) mySessionCallback
103+ myServer :: R. Connection -> JWT. JWTSettings -> Server MyAPI
104+ myServer conn jwtSettings =
100105 Servant. hoistServerWithContext apiProxy ctxProxy (unRedis conn) $
101- serviceProviderEndpoints
106+ serviceProviderEndpoints jwtSettings
102107 :<|> mayAuthedEndpoint
103108 :<|> authedEndpoint
104109 :<|> errorEndpoint
@@ -108,10 +113,8 @@ main = do
108113 cookieDefaultTTL = Just $ 60 * 60 * 24 * 7 -- 1 week
109114 cookieSettings :: Cookies. CookieSettings
110115 cookieSettings = Cookies. defaultCookieSettings onLocal cookieDefaultTTL
111- jwtSettings :: JWT. JWTSettings
112- jwtSettings = JWT. defaultJWTSettings hs256Key acceptedAudiences issuer
113- spConfig :: ServiceProviderConfig
114- spConfig =
116+ spConfig :: JWT. JWTSettings -> ServiceProviderConfig
117+ spConfig jwtSettings =
115118 ServiceProviderConfig
116119 { cookieSettings,
117120 jwtSettings = jwtSettings,
@@ -124,7 +127,13 @@ main = do
124127 sessionCookieKey
125128 }
126129 onLocal = True
127- hs256Key = " gpeakbroleymbscyqzrcalpemrjayhur"
130+ -- Ensure you use cryptographically secure 32-byte keys in production use.
131+ -- And don't re-use keys.
132+ hs256Key = " example-32-byte-hs256Key-jayhuxr"
133+ edDSAKey = " example-32-byte-edDSAKey-dxencne"
134+ legacyKey = JWT. KeyDescription {JWT. key = hs256Key, JWT. alg = JWT. HS256 }
135+ signingKey = JWT. KeyDescription {JWT. key = edDSAKey, JWT. alg = JWT. Ed25519 }
136+ rotatedKeys = Set. empty
128137 api = unsafeURI " http://cloud:3030"
129138 serviceAudience = api
130139 acceptedAudiences = Set. singleton serviceAudience
0 commit comments