Skip to content

Commit

Permalink
Completely review internal implementation & exposed API
Browse files Browse the repository at this point in the history
- Some stuff exposed from OAuth.Decode didn't make much sense to be exposed, removing the need for having
such module prevents us from having to expose them. Plus, it re-enable a clearer division of one module per flow

- Also made the 'Token' type opaque

- The 'Authentication' ADT has been broke down into 4 different records that are now
  only exposed in their respective modules.

- The Internals of the 'Token' type isn't exposed anymore'

- JSON decoders and Querystring parsers have been more-or-less unified. Note that
  there's still work to do on them to fully capture all possible errors.

- We now expose a 'OAuth.Refresh' module, not sure why it wasn't exposed before...

- Some documentation quirks have been fixed

- Some renaming to increase consistency and readability (e.g. 'use' -> 'useToken',
  'Err' -> 'ErrorResponse' etc.)

- The 'ParseErr' has been renamed and reviewed into a 'ParseResult' which now contains
  a 'Success' branch. This avoids the cumbersome 'Result ParseErr a' with 'ParseErr'
  being an ADT

- Mostly, I was wrongly re-using structures for authorization / authentication between flows
  whereas there is subtles differences (especially about this `state`)
  parameter.

- Put a great amount of efforts in "normalizing" decoders and query
parsers to have them more 'pure' and operating at one level at a time

- Remove the 'FailedToParse' and 'InvalidResponse' from the parsers
results, in favor of Empty. It means that all-in-all, we can't really
make the difference between an invalid response from the server and an
absence of token. That's a debatable choice.

- More renaming for consistency

- Fixed an issue where 'ErrorCode' wouldn't parse if provided with a
  custom code. This commi ditches the 'Unknown' constructor in favor of
  'Custom String'. For now, we don't enforce any rules on that 'String'
  which is wrong since the spec clearly make a statement about the shape
  of that error code. Will do.
  • Loading branch information
KtorZ committed Sep 6, 2018
1 parent 4801593 commit 0ac7d90
Show file tree
Hide file tree
Showing 10 changed files with 993 additions and 948 deletions.
13 changes: 7 additions & 6 deletions elm.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "package",
"name": "truqu/elm-oauth2",
"version": "3.0.0",
"version": "4.0.0",
"license": "MIT",
"summary": "OAuth 2.0 client-side utils",
"exposed-modules": [
Expand All @@ -10,16 +10,17 @@
"OAuth.Implicit",
"OAuth.ClientCredentials",
"OAuth.Password",
"OAuth.Decode"
"OAuth.Refresh"
],
"dependencies": {
"elm/url": "1.0.0 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/url": "1.0.0 <= v < 2.0.0",
"elm/http": "1.0.0 <= v < 2.0.0",
"elm/browser": "1.0.0 <= v < 2.0.0",
"elm/json": "1.0.0 <= v < 2.0.0",
"truqu/elm-base64": "2.0.0 <= v < 3.0.0"
"truqu/elm-base64": "2.0.0 <= v < 3.0.0",

"elm/html": "1.0.0 <= v < 2.0.0",
"elm/browser": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {},
"elm-version": "0.19.0 <= v < 0.20.0"
Expand Down
162 changes: 35 additions & 127 deletions examples/implicit/Main.elm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ main =

type alias Model =
{ redirectUri : Url
, state : String
, error : Maybe String
, token : Maybe OAuth.Token
, profile : Maybe Profile
Expand All @@ -51,27 +52,6 @@ profileDecoder =
(Json.field "picture" Json.string)


preModel : Int -> Model -> String
preModel n model =
preRecord n
"Model"
[ ( "redirectUri", preString 30 <| Url.toString model.redirectUri )
, ( "error", preMaybe (preString 30) model.error )
, ( "token", preMaybe (\s -> preString 30 <| OAuth.showToken s) model.token )
, ( "profile", preMaybe (preProfile (n + 1)) model.profile )
]


preProfile : Int -> Profile -> String
preProfile n profile =
preRecord n
"Profile"
[ ( "email", preString 30 profile.email )
, ( "name", preString 30 profile.name )
, ( "picture", preString 30 profile.picture )
]



-- Msg

Expand All @@ -90,24 +70,33 @@ type
-- init


makeInitModel : Url -> Model
makeInitModel origin =
{ redirectUri = { origin | query = Nothing, fragment = Nothing }
, state = "CSRF" -- NOTE In theory, this state is securely generated before each request and stored somewhere.
, error = Nothing
, token = Nothing
, profile = Nothing
}


init : () -> Url -> Key -> ( Model, Cmd Msg )
init _ origin navKey =
init _ origin _ =
let
model =
{ redirectUri = origin
, error = Nothing
, token = Nothing
, profile = Nothing
}
makeInitModel origin
in
case OAuth.Implicit.parse origin of
Ok { token } ->
case OAuth.Implicit.parseToken origin of
OAuth.Implicit.Success { token } ->
( { model | token = Just token }
, getUserProfile profileEndpoint token
)

Err err ->
( { model | error = showParseErr err }
OAuth.Implicit.Empty ->
( model, Cmd.none )

OAuth.Implicit.Error { error } ->
( { model | error = Just <| OAuth.errorCodeToString error }
, Cmd.none
)

Expand All @@ -118,33 +107,14 @@ getUserProfile endpoint token =
Http.request
{ method = "GET"
, body = Http.emptyBody
, headers = OAuth.use token []
, headers = OAuth.useToken token []
, withCredentials = False
, url = Url.toString endpoint
, expect = Http.expectJson profileDecoder
, timeout = Nothing
}


showParseErr : OAuth.ParseErr -> Maybe String
showParseErr oauthErr =
case oauthErr of
OAuth.Empty ->
Nothing

OAuth.OAuthErr err ->
Just <| OAuth.showErrCode err.error

OAuth.FailedToParse ->
Just "Failed to parse the origin URL"

OAuth.Missing params ->
Just <| "Missing expected parameter(s) from the response: " ++ String.join ", " params

OAuth.Invalid params ->
Just <| "Invalid parameter(s) from the response: " ++ String.join ", " params



-- update

Expand All @@ -156,15 +126,17 @@ update msg model =
( model, Cmd.none )

SignInRequested ->
let
auth =
{ clientId = clientId
, redirectUri = model.redirectUri
, scope = [ "email", "profile" ]
, state = Nothing
, url = authorizationEndpoint
}
in
( model
, OAuth.Implicit.authorize
{ clientId = clientId
, redirectUri = model.redirectUri
, responseType = OAuth.Token
, scope = [ "email", "profile" ]
, state = Nothing
, url = authorizationEndpoint
}
, auth |> OAuth.Implicit.makeAuthUrl |> Url.toString |> Navigation.load
)

GotUserInfo res ->
Expand Down Expand Up @@ -230,27 +202,10 @@ viewBody model content =
, style "color" "#ffffff"
]
[ text msg ]
, div
[ style "display" "flex"
, style "align-items" "center"
, style "justify-content" "center"
, style "width" "70%"
]
[ content
, pre [ style "padding" "2em" ] [ text (preModel 1 model) ]
]
, content
]



-- type alias Model =
-- { redirectUri : Url
-- , error : Maybe String
-- , token : Maybe OAuth.Token
-- , profile : Maybe Profile
-- }


viewSignInButton : Html Msg
viewSignInButton =
button
Expand Down Expand Up @@ -306,67 +261,20 @@ viewProfile profile =



-- Formatting Helpers


preString : Int -> String -> String
preString maxSize str =
if String.length str > maxSize then
String.left maxSize str ++ "..."

else
str


preRecord : Int -> String -> List ( String, String ) -> String
preRecord n name fields =
let
preField ( k, v ) =
k ++ " = " ++ v

padded s =
"\n" ++ String.repeat (n * 2) " " ++ s
in
case fields of
[] ->
name

[ fst ] ->
name ++ " = { " ++ preField fst ++ " }"

fst :: rest ->
String.concat
[ name ++ " =" ++ padded "{ " ++ preField fst ++ padded ", "
, String.join (padded ", ") (List.map preField rest)
, padded "}"
]


preMaybe : (a -> String) -> Maybe a -> String
preMaybe pre m =
case m of
Nothing ->
"Nothing"

Just a ->
"Just (" ++ pre a ++ ")"



-- Constants / Google APIs endpoints
-- Demo clientId, configured to target the github repository's gh-pages only


clientId : String
clientId =
"909608474358-sucp6e4js3nvfkfnab5t69qoelampt3t.apps.googleusercontent.com"
"909608474358-apio86lq9hvjobd3hiepgtrclthnc4q0.apps.googleusercontent.com"


authorizationEndpoint : Url
authorizationEndpoint =
{ protocol = Https
, host = "accounts.google.com"
, path = "/o/oauth2/v2/auth/"
, path = "/o/oauth2/v2/auth"
, port_ = Nothing
, query = Nothing
, fragment = Nothing
Expand All @@ -377,7 +285,7 @@ profileEndpoint : Url
profileEndpoint =
{ protocol = Https
, host = "www.googleapis.com"
, path = "/oauth2/v1/userinfo/"
, path = "/oauth2/v1/userinfo"
, port_ = Nothing
, query = Nothing
, fragment = Nothing
Expand Down
Loading

0 comments on commit 0ac7d90

Please sign in to comment.