git.iko.soy/typeable/random-token
on master help sign in
Cleanup sso (#4648)

Clean sso api packages from business logic and TH

commitc1b18935f622b0e34191eef08527708e09b4e27c authorAlexey Uimanov <s9gf4ult@gmail.com> dateTue 23 Jun 2020, 12:46 (6 years ago)
5 files changed+197
Setup.hs+2 random-token.cabal+104 spec/Test.hs+1 spec/TokenSpec.hs+12 src/Random/Token.hs+78
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
random-token.cabalnew file+104
@@ -0,0 +1,104 @@
1+name: random-token
2+version: 0.1.0.0
3+synopsis: Implementation of random token generation and parsing
4+-- description:
5+author: typeable.io
6+maintainer: s9gf4ult@gmail.com
7+copyright: 2020 Author name here
8+category: Web
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+library
13+ hs-source-dirs: src
14+ ghc-options: -Wall
15+ exposed-modules: Random.Token
16+ build-depends: base >= 4.7 && < 5
17+ , QuickCheck
18+ , aeson
19+ , base64-bytestring
20+ , bytestring
21+ , deepseq
22+ , http-api-data
23+ , text
24+ if !impl(ghcjs)
25+ build-depends: cryptonite
26+ , postgresql-simple
27+ default-language: Haskell2010
28+ default-extensions: CPP
29+ , ConstraintKinds
30+ , DataKinds
31+ , DeriveFoldable
32+ , DeriveFunctor
33+ , DeriveGeneric
34+ , DeriveTraversable
35+ , DuplicateRecordFields
36+ , FlexibleContexts
37+ , FlexibleInstances
38+ , FunctionalDependencies
39+ , GADTs
40+ , GeneralizedNewtypeDeriving
41+ , LambdaCase
42+ , MultiParamTypeClasses
43+ , MultiWayIf
44+ , NumDecimals
45+ , OverloadedStrings
46+ , PolyKinds
47+ , QuasiQuotes
48+ , RankNTypes
49+ , RecordWildCards
50+ , RoleAnnotations
51+ , ScopedTypeVariables
52+ , StandaloneDeriving
53+ , TemplateHaskell
54+ , TupleSections
55+ , TypeApplications
56+ , TypeFamilies
57+ , TypeOperators
58+ , UndecidableInstances
59+ , ViewPatterns
60+
61+test-suite spec
62+ type: exitcode-stdio-1.0
63+ hs-source-dirs: spec
64+ main-is: Test.hs
65+ other-modules: TokenSpec
66+ default-language: Haskell2010
67+ build-depends: base >= 4.10 && <4.13
68+ , hspec
69+ , random-token
70+ ghc-options: -Wall
71+ -threaded
72+ -rtsopts
73+ "-with-rtsopts=-N -A64m -qb0 -n4m -T -I1"
74+ ghc-prof-options: -fprof-auto
75+ default-extensions: ConstraintKinds
76+ , DataKinds
77+ , DeriveFoldable
78+ , DeriveFunctor
79+ , DeriveGeneric
80+ , DeriveTraversable
81+ , DuplicateRecordFields
82+ , FlexibleContexts
83+ , FlexibleInstances
84+ , FunctionalDependencies
85+ , GADTs
86+ , GeneralizedNewtypeDeriving
87+ , LambdaCase
88+ , MultiParamTypeClasses
89+ , MultiWayIf
90+ , NumDecimals
91+ , OverloadedStrings
92+ , PolyKinds
93+ , QuasiQuotes
94+ , RankNTypes
95+ , RecordWildCards
96+ , ScopedTypeVariables
97+ , StandaloneDeriving
98+ , TemplateHaskell
99+ , TupleSections
100+ , TypeApplications
101+ , TypeFamilies
102+ , TypeOperators
103+ , UndecidableInstances
104+ , ViewPatterns
spec/Test.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
spec/TokenSpec.hsnew file+12
@@ -0,0 +1,12 @@
1+module TokenSpec where
2+
3+import Random.Token
4+import Test.Hspec
5+import Test.Hspec.QuickCheck
6+
7+spec :: Spec
8+spec = do
9+ prop "asciiToToken . tokenToAscii ~ id" $ \ (t :: Token ()) ->
10+ t == (either (error . show) id $ asciiToToken $ tokenToAscii t)
11+ prop "textToToken . tokenToText ~ id" $ \ (t :: Token ()) ->
12+ t == (either (error . show) id $ textToToken $ tokenToText t)
src/Random/Token.hsnew file+78
@@ -0,0 +1,78 @@
1+module Random.Token
2+ ( Token
3+ , tokenToAscii
4+ , tokenToText
5+ , asciiToToken
6+ , textToToken
7+#ifndef ghcjs_HOST_OS
8+ , generateToken
9+#endif
10+ ) where
11+
12+import Control.DeepSeq
13+import Data.Aeson
14+import Data.Bifunctor
15+import Data.ByteString as BS
16+import Data.ByteString.Base64.URL as B64
17+import Data.List as L
18+import Data.Text as T
19+import Data.Text.Encoding as T
20+import Data.Typeable
21+import Test.QuickCheck.Arbitrary
22+import Web.HttpApiData
23+
24+#ifndef ghcjs_HOST_OS
25+import Crypto.Random as R
26+import Database.PostgreSQL.Simple.FromField
27+import Database.PostgreSQL.Simple.ToField
28+#endif
29+
30+-- | 16 bytes lenght random value parametrized by label.
31+newtype Token (a :: k) = Token ByteString
32+ deriving (Ord, Eq, Show, NFData)
33+
34+type role Token nominal
35+
36+instance Arbitrary (Token a) where
37+ arbitrary = fmap (Token . BS.pack) $ sequence $ L.replicate 16 arbitrary
38+
39+instance FromJSON (Token a) where
40+ parseJSON v = do
41+ t <- parseJSON v
42+ either fail return $ textToToken t
43+
44+instance ToJSON (Token a) where
45+ toJSON = toJSON . tokenToText
46+
47+#ifndef ghcjs_HOST_OS
48+instance forall a. (Typeable (Token a), Typeable a) => FromField (Token a) where
49+ fromField fld bs = do
50+ t <- fromField fld bs
51+ case textToToken t of
52+ Left e -> returnError ConversionFailed fld e
53+ Right a -> return a
54+
55+instance ToField (Token a) where
56+ toField = toField . tokenToText
57+
58+generateToken :: (MonadRandom m) => m (Token a)
59+generateToken = Token <$> R.getRandomBytes 16
60+#endif
61+
62+instance FromHttpApiData (Token a) where
63+ parseQueryParam = first T.pack . textToToken
64+
65+instance ToHttpApiData (Token a) where
66+ toQueryParam = tokenToText
67+
68+tokenToAscii :: Token a -> ByteString
69+tokenToAscii (Token bs) = B64.encode bs
70+
71+tokenToText :: Token a -> Text
72+tokenToText = T.decodeUtf8 . tokenToAscii
73+
74+asciiToToken :: ByteString -> Either String (Token a)
75+asciiToToken bs = Token <$> B64.decode bs
76+
77+textToToken :: Text -> Either String (Token a)
78+textToToken = asciiToToken . T.encodeUtf8