authentication: Support Github/Gmail social login
Yona support social login, Github and Gmail. It doesn't need to sign up anymore. This feature was easily developed thanks to "Play Athenticate" See: https://github.com/joscha/play-authenticate
@2bb95f73379676f5921f8aaee340cd96a40d139d
--- .gitignore
+++ .gitignore
... | ... | @@ -31,3 +31,4 @@ |
31 | 31 |
conf/application-logger.xml |
32 | 32 |
.java-version |
33 | 33 |
migration-client |
34 |
+conf/play-authenticate/mine.conf |
--- app/Global.java
+++ app/Global.java
... | ... | @@ -20,6 +20,9 @@ |
20 | 20 |
*/ |
21 | 21 |
|
22 | 22 |
import com.avaje.ebean.Ebean; |
23 |
+import com.feth.play.module.pa.PlayAuthenticate; |
|
24 |
+import com.feth.play.module.pa.exceptions.AccessDeniedException; |
|
25 |
+import com.feth.play.module.pa.exceptions.AuthException; |
|
23 | 26 |
import com.typesafe.config.ConfigFactory; |
24 | 27 |
import controllers.SvnApp; |
25 | 28 |
import controllers.UserApp; |
... | ... | @@ -34,11 +37,8 @@ |
34 | 37 |
import play.api.mvc.Handler; |
35 | 38 |
import play.data.Form; |
36 | 39 |
import play.libs.F.Promise; |
37 |
-import play.mvc.Action; |
|
38 |
-import play.mvc.Http; |
|
40 |
+import play.mvc.*; |
|
39 | 41 |
import play.mvc.Http.RequestHeader; |
40 |
-import play.mvc.Result; |
|
41 |
-import play.mvc.Results; |
|
42 | 42 |
import utils.*; |
43 | 43 |
import views.html.welcome.restart; |
44 | 44 |
import views.html.welcome.secret; |
... | ... | @@ -63,6 +63,7 @@ |
63 | 63 |
|
64 | 64 |
import static play.data.Form.form; |
65 | 65 |
import static play.mvc.Results.badRequest; |
66 |
+import static play.mvc.Results.redirect; |
|
66 | 67 |
|
67 | 68 |
|
68 | 69 |
public class Global extends GlobalSettings { |
... | ... | @@ -76,10 +77,12 @@ |
76 | 77 |
|
77 | 78 |
private ConfigFile configFile = new ConfigFile("config", "application.conf"); |
78 | 79 |
private ConfigFile loggerConfigFile = new ConfigFile("logger", "application-logger.xml"); |
80 |
+ private ConfigFile oAuthProviderConfFile = new ConfigFile("conf", "social-login.conf"); |
|
79 | 81 |
|
80 | 82 |
@Override |
81 | 83 |
public Configuration onLoadConfig(play.Configuration config, File path, ClassLoader classloader) { |
82 | 84 |
initLoggerConfig(); |
85 |
+ initAuthProviderConfig(); |
|
83 | 86 |
return initConfig(classloader); |
84 | 87 |
} |
85 | 88 |
|
... | ... | @@ -133,6 +136,23 @@ |
133 | 136 |
} |
134 | 137 |
} |
135 | 138 |
|
139 |
+ /** |
|
140 |
+ * Creates play-authenticate/mine.conf by default if necessary |
|
141 |
+ */ |
|
142 |
+ private void initAuthProviderConfig() { |
|
143 |
+ try { |
|
144 |
+ if (!oAuthProviderConfFile.isLocationSpecified() && !oAuthProviderConfFile.getPath().toFile().exists()) { |
|
145 |
+ try { |
|
146 |
+ oAuthProviderConfFile.createByDefault(); |
|
147 |
+ } catch (Exception e) { |
|
148 |
+ play.Logger.error("Failed to initialize social-login.conf", e); |
|
149 |
+ } |
|
150 |
+ } |
|
151 |
+ } catch (URISyntaxException e) { |
|
152 |
+ play.Logger.error("Failed to check whether the social-login.conf file exists", e); |
|
153 |
+ } |
|
154 |
+ } |
|
155 |
+ |
|
136 | 156 |
@Override |
137 | 157 |
public void onStart(Application app) { |
138 | 158 |
isSecretInvalid = equalsDefaultSecret(); |
... | ... | @@ -150,6 +170,59 @@ |
150 | 170 |
YobiUpdate.onStart(); |
151 | 171 |
mailboxService.start(); |
152 | 172 |
} |
173 |
+ |
|
174 |
+ PlayAuthenticate.setResolver(new PlayAuthenticate.Resolver() { |
|
175 |
+ |
|
176 |
+ @Override |
|
177 |
+ public Call login() { |
|
178 |
+ // Your login page |
|
179 |
+ return routes.Application.index(); |
|
180 |
+ } |
|
181 |
+ |
|
182 |
+ @Override |
|
183 |
+ public Call afterAuth() { |
|
184 |
+ // The user will be redirected to this page after authentication |
|
185 |
+ // if no original URL was saved |
|
186 |
+ return routes.Application.index(); |
|
187 |
+ } |
|
188 |
+ |
|
189 |
+ @Override |
|
190 |
+ public Call afterLogout() { |
|
191 |
+ return routes.Application.index(); |
|
192 |
+ } |
|
193 |
+ |
|
194 |
+ @Override |
|
195 |
+ public Call auth(final String provider) { |
|
196 |
+ return routes.Application.oAuth(provider); |
|
197 |
+ } |
|
198 |
+ |
|
199 |
+ @Override |
|
200 |
+ public Call onException(final AuthException e) { |
|
201 |
+ if (e instanceof AccessDeniedException) { |
|
202 |
+ return routes.Application |
|
203 |
+ .oAuthDenied(((AccessDeniedException) e) |
|
204 |
+ .getProviderKey()); |
|
205 |
+ } |
|
206 |
+ |
|
207 |
+ // more custom problem handling here... |
|
208 |
+ |
|
209 |
+ return super.onException(e); |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ @Override |
|
213 |
+ public Call askLink() { |
|
214 |
+ // We don't support moderated account linking in this sample. |
|
215 |
+ // See the play-authenticate-usage project for an example |
|
216 |
+ return null; |
|
217 |
+ } |
|
218 |
+ |
|
219 |
+ @Override |
|
220 |
+ public Call askMerge() { |
|
221 |
+ // We don't support moderated account merging in this sample. |
|
222 |
+ // See the play-authenticate-usage project for an example |
|
223 |
+ return null; |
|
224 |
+ } |
|
225 |
+ }); |
|
153 | 226 |
} |
154 | 227 |
|
155 | 228 |
private boolean equalsDefaultSecret() { |
--- app/assets/stylesheets/less/_override.less
+++ app/assets/stylesheets/less/_override.less
... | ... | @@ -365,3 +365,7 @@ |
365 | 365 |
margin-bottom: -1px; |
366 | 366 |
} |
367 | 367 |
|
368 |
+.oauth-login-btn{ |
|
369 |
+ display: block; |
|
370 |
+ margin: 10px 0; |
|
371 |
+} |
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -6638,3 +6638,17 @@ |
6638 | 6638 |
margin-left: 10px; |
6639 | 6639 |
width: 400px; |
6640 | 6640 |
} |
6641 |
+ |
|
6642 |
+.auth-provider-logo { |
|
6643 |
+ font-family: 'Roboto', sans-serif; |
|
6644 |
+ svg { |
|
6645 |
+ vertical-align: middle; |
|
6646 |
+ } |
|
6647 |
+ .github { |
|
6648 |
+ width: 30px; |
|
6649 |
+ display: inline-block; |
|
6650 |
+ margin-left: -4px; |
|
6651 |
+ margin-top: 3px; |
|
6652 |
+ margin-bottom: 3px; |
|
6653 |
+ } |
|
6654 |
+} |
--- app/controllers/Application.java
+++ app/controllers/Application.java
... | ... | @@ -1,44 +1,63 @@ |
1 | 1 |
/** |
2 |
- * Yobi, Project Hosting SW |
|
3 |
- * |
|
4 |
- * Copyright 2012 NAVER Corp. |
|
5 |
- * http://yobi.io |
|
6 |
- * |
|
7 |
- * @author Sangcheol Hwang |
|
8 |
- * |
|
9 |
- * Licensed under the Apache License, Version 2.0 (the "License"); |
|
10 |
- * you may not use this file except in compliance with the License. |
|
11 |
- * You may obtain a copy of the License at |
|
12 |
- * |
|
13 |
- * http://www.apache.org/licenses/LICENSE-2.0 |
|
14 |
- * |
|
15 |
- * Unless required by applicable law or agreed to in writing, software |
|
16 |
- * distributed under the License is distributed on an "AS IS" BASIS, |
|
17 |
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
18 |
- * See the License for the specific language governing permissions and |
|
19 |
- * limitations under the License. |
|
20 |
- */ |
|
2 |
+ * Yona, 21st Century Project Hosting SW |
|
3 |
+ * <p> |
|
4 |
+ * Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+ * https://yona.io |
|
6 |
+ **/ |
|
21 | 7 |
package controllers; |
22 | 8 |
|
9 |
+import com.feth.play.module.pa.PlayAuthenticate; |
|
23 | 10 |
import controllers.annotation.AnonymousCheck; |
11 |
+import jsmessages.JsMessages; |
|
24 | 12 |
import models.Project; |
13 |
+import models.UserCredential; |
|
25 | 14 |
import play.Logger; |
26 | 15 |
import play.mvc.Controller; |
16 |
+import play.mvc.Http; |
|
27 | 17 |
import play.mvc.Result; |
28 | 18 |
import playRepository.RepositoryService; |
29 | 19 |
import views.html.error.notfound_default; |
30 | 20 |
import views.html.index.index; |
31 |
-import jsmessages.JsMessages; |
|
32 | 21 |
|
33 |
-import java.io.File; |
|
22 |
+import static com.feth.play.module.pa.controllers.Authenticate.*; |
|
34 | 23 |
|
35 | 24 |
public class Application extends Controller { |
25 |
+ public static final String FLASH_MESSAGE_KEY = "message"; |
|
26 |
+ public static final String FLASH_ERROR_KEY = "error"; |
|
36 | 27 |
|
37 | 28 |
@AnonymousCheck |
38 | 29 |
public static Result index() { |
39 | 30 |
return ok(index.render(UserApp.currentUser())); |
40 | 31 |
} |
41 | 32 |
|
33 |
+ public static Result oAuth(final String provider) { |
|
34 |
+ return authenticate(provider); |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ public static Result oAuthLogout() { |
|
38 |
+ UserApp.logout(); |
|
39 |
+ return logout(); |
|
40 |
+ } |
|
41 |
+ |
|
42 |
+ public static Result oAuthDenied(final String providerKey) { |
|
43 |
+ noCache(response()); |
|
44 |
+ flash(FLASH_ERROR_KEY, |
|
45 |
+ "You need to accept the OAuth connection in order to use this website!"); |
|
46 |
+ return redirect(routes.Application.index()); |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ public static UserCredential getLocalUser(final Http.Session session) { |
|
50 |
+ final UserCredential localUser = UserCredential.findByAuthUserIdentity(PlayAuthenticate |
|
51 |
+ .getUser(session)); |
|
52 |
+ return localUser; |
|
53 |
+ } |
|
54 |
+ |
|
55 |
+ public static UserCredential getLocalUser() { |
|
56 |
+ final UserCredential localUser = UserCredential.findByAuthUserIdentity(PlayAuthenticate |
|
57 |
+ .getUser(session())); |
|
58 |
+ return localUser; |
|
59 |
+ } |
|
60 |
+ |
|
42 | 61 |
public static Result removeTrailer(String paths){ |
43 | 62 |
String path = request().path(); |
44 | 63 |
if( path.charAt(path.length()-1) == '/' ) { |
+++ app/controllers/Restricted.java
... | ... | @@ -0,0 +1,16 @@ |
1 | +package controllers; | |
2 | + | |
3 | +import models.UserCredential; | |
4 | +import play.mvc.Controller; | |
5 | +import play.mvc.Result; | |
6 | +import play.mvc.Security; | |
7 | +import views.html.restricted; | |
8 | + | |
9 | +@Security.Authenticated(Secured.class) | |
10 | +public class Restricted extends Controller { | |
11 | + | |
12 | + public static Result index() { | |
13 | + final UserCredential localUser = Application.getLocalUser(session()); | |
14 | + return ok(restricted.render(localUser)); | |
15 | + } | |
16 | +} |
+++ app/controllers/Secured.java
... | ... | @@ -0,0 +1,27 @@ |
1 | +package controllers; | |
2 | + | |
3 | +import com.feth.play.module.pa.PlayAuthenticate; | |
4 | +import com.feth.play.module.pa.user.AuthUser; | |
5 | +import play.mvc.Http.Context; | |
6 | +import play.mvc.Result; | |
7 | +import play.mvc.Security; | |
8 | + | |
9 | +public class Secured extends Security.Authenticator { | |
10 | + | |
11 | + @Override | |
12 | + public String getUsername(final Context ctx) { | |
13 | + final AuthUser u = PlayAuthenticate.getUser(ctx.session()); | |
14 | + | |
15 | + if (u != null) { | |
16 | + return u.getId(); | |
17 | + } else { | |
18 | + return null; | |
19 | + } | |
20 | + } | |
21 | + | |
22 | + @Override | |
23 | + public Result onUnauthorized(final Context ctx) { | |
24 | + ctx.flash().put(Application.FLASH_MESSAGE_KEY, "Nice try, but you need to log in first!"); | |
25 | + return redirect(routes.Application.index()); | |
26 | + } | |
27 | +}(No newline at end of file) |
--- app/controllers/UserApp.java
+++ app/controllers/UserApp.java
... | ... | @@ -1,28 +1,16 @@ |
1 | 1 |
/** |
2 |
- * Yobi, Project Hosting SW |
|
3 |
- * |
|
4 |
- * Copyright 2013 NAVER Corp. |
|
5 |
- * http://yobi.io |
|
6 |
- * |
|
7 |
- * Licensed under the Apache License, Version 2.0 (the "License"); |
|
8 |
- * you may not use this file except in compliance with the License. |
|
9 |
- * You may obtain a copy of the License at |
|
10 |
- * |
|
11 |
- * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 |
- * |
|
13 |
- * Unless required by applicable law or agreed to in writing, software |
|
14 |
- * distributed under the License is distributed on an "AS IS" BASIS, |
|
15 |
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
16 |
- * See the License for the specific language governing permissions and |
|
17 |
- * limitations under the License. |
|
18 |
- */ |
|
19 |
- |
|
2 |
+ * Yona, 21st Century Project Hosting SW |
|
3 |
+ * <p> |
|
4 |
+ * Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+ * https://yona.io |
|
6 |
+ **/ |
|
20 | 7 |
package controllers; |
21 | 8 |
|
22 | 9 |
import com.avaje.ebean.ExpressionList; |
23 | 10 |
import com.avaje.ebean.Page; |
24 | 11 |
import com.avaje.ebean.annotation.Transactional; |
25 | 12 |
import com.fasterxml.jackson.databind.node.ObjectNode; |
13 |
+import com.feth.play.module.pa.PlayAuthenticate; |
|
26 | 14 |
import controllers.annotation.AnonymousCheck; |
27 | 15 |
import models.*; |
28 | 16 |
import models.enumeration.Operation; |
... | ... | @@ -121,7 +109,14 @@ |
121 | 109 |
if(StringUtils.isEmpty(redirectUrl) && !StringUtils.equals(loginFormUrl, referer)) { |
122 | 110 |
redirectUrl = request().getHeader("Referer"); |
123 | 111 |
} |
124 |
- return ok(login.render("title.login", form(AuthInfo.class), redirectUrl)); |
|
112 |
+ |
|
113 |
+ //Assume oAtuh is passed but not linked with existed account |
|
114 |
+ if(PlayAuthenticate.isLoggedIn(session())){ |
|
115 |
+ UserApp.linkWithExistedOrCreateLocalUser(); |
|
116 |
+ return redirect(redirectUrl); |
|
117 |
+ } else { |
|
118 |
+ return ok(login.render("title.login", form(AuthInfo.class), redirectUrl)); |
|
119 |
+ } |
|
125 | 120 |
} |
126 | 121 |
|
127 | 122 |
public static Result logout() { |
... | ... | @@ -329,6 +324,56 @@ |
329 | 324 |
addUserInfoToSession(user); |
330 | 325 |
} |
331 | 326 |
return redirect(routes.Application.index()); |
327 |
+ } |
|
328 |
+ } |
|
329 |
+ |
|
330 |
+ private static String newLoginIdWithoutDup(final String candidate, int num) { |
|
331 |
+ String newLoginIdSuggestion = candidate + "" + num; |
|
332 |
+ if(User.findByLoginId(newLoginIdSuggestion).isAnonymous()){ |
|
333 |
+ return newLoginIdSuggestion; |
|
334 |
+ } else { |
|
335 |
+ num = num + 1; |
|
336 |
+ return newLoginIdWithoutDup(newLoginIdSuggestion, num); |
|
337 |
+ } |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ public static void createLocalUserWithOAuth(UserCredential userCredential){ |
|
341 |
+ User user = new User(); |
|
342 |
+ String loginIdCandidate = userCredential.email.substring(0, userCredential.email.indexOf("@")); |
|
343 |
+ |
|
344 |
+ user.loginId = generateLoginId(user, loginIdCandidate); |
|
345 |
+ user.name = userCredential.name; |
|
346 |
+ user.email = userCredential.email; |
|
347 |
+ |
|
348 |
+ RandomNumberGenerator rng = new SecureRandomNumberGenerator(); |
|
349 |
+ user.password = rng.nextBytes().toBase64(); // random password because created with oAuth |
|
350 |
+ |
|
351 |
+ User created = createNewUser(user); |
|
352 |
+ |
|
353 |
+ if (created.state == UserState.LOCKED) { |
|
354 |
+ flash(Constants.INFO, "user.signup.requested"); |
|
355 |
+ } else { |
|
356 |
+ addUserInfoToSession(created); |
|
357 |
+ } |
|
358 |
+ |
|
359 |
+ //Also, update userCredential |
|
360 |
+ userCredential.loginId = created.loginId; |
|
361 |
+ userCredential.user = created; |
|
362 |
+ userCredential.update(); |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ private static String generateLoginId(User user, String loginIdCandidate) { |
|
366 |
+ String loginId = null; |
|
367 |
+ User sameLoginIdUser = User.findByLoginId(loginIdCandidate); |
|
368 |
+ if (sameLoginIdUser.isAnonymous()) { |
|
369 |
+ return loginIdCandidate; |
|
370 |
+ } else { |
|
371 |
+ sameLoginIdUser = User.findByLoginId(loginIdCandidate + "-yona"); |
|
372 |
+ if (sameLoginIdUser.isAnonymous()) { |
|
373 |
+ return loginIdCandidate + "-yona"; // first dup, then use suffix "-yona" |
|
374 |
+ } else { |
|
375 |
+ return newLoginIdWithoutDup(loginIdCandidate, 2); |
|
376 |
+ } |
|
332 | 377 |
} |
333 | 378 |
} |
334 | 379 |
|
... | ... | @@ -936,6 +981,28 @@ |
936 | 981 |
session(SESSION_KEY, key); |
937 | 982 |
} |
938 | 983 |
|
984 |
+ public static void linkWithExistedOrCreateLocalUser() { |
|
985 |
+ final UserCredential oAuthUser = UserCredential.findByAuthUserIdentity(PlayAuthenticate |
|
986 |
+ .getUser(Http.Context.current().session())); |
|
987 |
+ User user = null; |
|
988 |
+ if (oAuthUser.loginId == null) { |
|
989 |
+ user = User.findByEmail(oAuthUser.email); |
|
990 |
+ } else { |
|
991 |
+ user = User.findByLoginId(oAuthUser.loginId); |
|
992 |
+ } |
|
993 |
+ |
|
994 |
+ if(PlayAuthenticate.isLoggedIn(session()) && user.isAnonymous()){ |
|
995 |
+ createLocalUserWithOAuth(oAuthUser); |
|
996 |
+ } else { |
|
997 |
+ if (oAuthUser.loginId == null) { |
|
998 |
+ oAuthUser.loginId = user.loginId; |
|
999 |
+ oAuthUser.user = user; |
|
1000 |
+ oAuthUser.update(); |
|
1001 |
+ } |
|
1002 |
+ UserApp.addUserInfoToSession(user); |
|
1003 |
+ } |
|
1004 |
+ } |
|
1005 |
+ |
|
939 | 1006 |
public static void updatePreferredLanguage() { |
940 | 1007 |
Http.Request request = Http.Context.current().request(); |
941 | 1008 |
User user = UserApp.currentUser(); |
+++ app/models/LinkedAccount.java
... | ... | @@ -0,0 +1,50 @@ |
1 | +package models; | |
2 | + | |
3 | +import com.feth.play.module.pa.user.AuthUser; | |
4 | +import play.db.ebean.Model; | |
5 | + | |
6 | +import javax.persistence.Entity; | |
7 | +import javax.persistence.Id; | |
8 | +import javax.persistence.ManyToOne; | |
9 | + | |
10 | +@Entity | |
11 | +public class LinkedAccount extends Model { | |
12 | + | |
13 | + private static final long serialVersionUID = 1L; | |
14 | + | |
15 | + @Id | |
16 | + public Long id; | |
17 | + | |
18 | + @ManyToOne | |
19 | + public UserCredential userCredential; | |
20 | + | |
21 | + public String providerUserId; | |
22 | + public String providerKey; | |
23 | + | |
24 | + public static final Finder<Long, LinkedAccount> find = new Finder<Long, LinkedAccount>( | |
25 | + Long.class, LinkedAccount.class); | |
26 | + | |
27 | + public static LinkedAccount findByProviderKey(final UserCredential userCredential, String key) { | |
28 | + return find.where().eq("userCredential", userCredential).eq("providerKey", key) | |
29 | + .findUnique(); | |
30 | + } | |
31 | + | |
32 | + public static LinkedAccount create(final AuthUser authUser) { | |
33 | + final LinkedAccount ret = new LinkedAccount(); | |
34 | + ret.update(authUser); | |
35 | + return ret; | |
36 | + } | |
37 | + | |
38 | + public void update(final AuthUser authUser) { | |
39 | + this.providerKey = authUser.getProvider(); | |
40 | + this.providerUserId = authUser.getId(); | |
41 | + } | |
42 | + | |
43 | + public static LinkedAccount create(final LinkedAccount acc) { | |
44 | + final LinkedAccount ret = new LinkedAccount(); | |
45 | + ret.providerKey = acc.providerKey; | |
46 | + ret.providerUserId = acc.providerUserId; | |
47 | + | |
48 | + return ret; | |
49 | + } | |
50 | +}(No newline at end of file) |
+++ app/models/UserCredential.java
... | ... | @@ -0,0 +1,142 @@ |
1 | +/** | |
2 | + * Yona, 21st Century Project Hosting SW | |
3 | + * <p> | |
4 | + * Copyright Yona & Yobi Authors & NAVER Corp. | |
5 | + * https://yona.io | |
6 | + **/ | |
7 | +package models; | |
8 | + | |
9 | +import com.avaje.ebean.Ebean; | |
10 | +import com.avaje.ebean.ExpressionList; | |
11 | +import com.feth.play.module.pa.user.AuthUser; | |
12 | +import com.feth.play.module.pa.user.AuthUserIdentity; | |
13 | +import com.feth.play.module.pa.user.EmailIdentity; | |
14 | +import com.feth.play.module.pa.user.NameIdentity; | |
15 | +import play.data.validation.Constraints; | |
16 | +import play.db.ebean.Model; | |
17 | + | |
18 | +import javax.persistence.*; | |
19 | +import java.util.*; | |
20 | + | |
21 | +@Entity | |
22 | +public class UserCredential extends Model { | |
23 | + private static final long serialVersionUID = 1L; | |
24 | + | |
25 | + @Id | |
26 | + public Long id; | |
27 | + | |
28 | + @OneToOne | |
29 | + public User user; | |
30 | + | |
31 | + public String loginId; | |
32 | + | |
33 | + @Constraints.Email | |
34 | + // if you make this unique, keep in mind that users *must* merge/link their | |
35 | + // accounts then on signup with additional providers | |
36 | + // @Column(unique = true) | |
37 | + public String email; | |
38 | + | |
39 | + public String name; | |
40 | + | |
41 | + public boolean active; | |
42 | + | |
43 | + public boolean emailValidated; | |
44 | + | |
45 | + @OneToMany(cascade = CascadeType.ALL) | |
46 | + public List<LinkedAccount> linkedAccounts; | |
47 | + | |
48 | + public static final Finder<Long, UserCredential> find = new Finder<Long, UserCredential>( | |
49 | + Long.class, UserCredential.class); | |
50 | + | |
51 | + public static boolean existsByAuthUserIdentity( | |
52 | + final AuthUserIdentity identity) { | |
53 | + final ExpressionList<UserCredential> exp = getAuthUserFind(identity); | |
54 | + return exp.findRowCount() > 0; | |
55 | + } | |
56 | + | |
57 | + private static ExpressionList<UserCredential> getAuthUserFind( | |
58 | + final AuthUserIdentity identity) { | |
59 | + return find.where().eq("active", true) | |
60 | + .eq("linkedAccounts.providerUserId", identity.getId()) | |
61 | + .eq("linkedAccounts.providerKey", identity.getProvider()); | |
62 | + } | |
63 | + | |
64 | + public static UserCredential findByAuthUserIdentity(final AuthUserIdentity identity) { | |
65 | + if (identity == null) { | |
66 | + return null; | |
67 | + } | |
68 | + return getAuthUserFind(identity).findUnique(); | |
69 | + } | |
70 | + | |
71 | + public void merge(final UserCredential otherUser) { | |
72 | + for (final LinkedAccount acc : otherUser.linkedAccounts) { | |
73 | + this.linkedAccounts.add(LinkedAccount.create(acc)); | |
74 | + } | |
75 | + // do all other merging stuff here - like resources, etc. | |
76 | + | |
77 | + // deactivate the merged user that got added to this one | |
78 | + otherUser.active = false; | |
79 | + Ebean.save(Arrays.asList(new UserCredential[] { otherUser, this })); | |
80 | + } | |
81 | + | |
82 | + public static UserCredential create(final AuthUser authUser) { | |
83 | + final UserCredential userCredential = new UserCredential(); | |
84 | + userCredential.active = true; | |
85 | + userCredential.linkedAccounts = Collections.singletonList(LinkedAccount | |
86 | + .create(authUser)); | |
87 | + | |
88 | + if (authUser instanceof EmailIdentity) { | |
89 | + final EmailIdentity identity = (EmailIdentity) authUser; | |
90 | + // Remember, even when getting them from FB & Co., emails should be | |
91 | + // verified within the application as a security breach there might | |
92 | + // break your security as well! | |
93 | + userCredential.email = identity.getEmail(); | |
94 | + userCredential.emailValidated = false; | |
95 | + } | |
96 | + | |
97 | + if (authUser instanceof NameIdentity) { | |
98 | + final NameIdentity identity = (NameIdentity) authUser; | |
99 | + final String name = identity.getName(); | |
100 | + if (name != null) { | |
101 | + userCredential.name = name; | |
102 | + } | |
103 | + } | |
104 | + | |
105 | + userCredential.save(); | |
106 | + return userCredential; | |
107 | + } | |
108 | + | |
109 | + public static void merge(final AuthUser oldUser, final AuthUser newUser) { | |
110 | + UserCredential.findByAuthUserIdentity(oldUser).merge( | |
111 | + UserCredential.findByAuthUserIdentity(newUser)); | |
112 | + } | |
113 | + | |
114 | + public Set<String> getProviders() { | |
115 | + final Set<String> providerKeys = new HashSet<String>( | |
116 | + linkedAccounts.size()); | |
117 | + for (final LinkedAccount acc : linkedAccounts) { | |
118 | + providerKeys.add(acc.providerKey); | |
119 | + } | |
120 | + return providerKeys; | |
121 | + } | |
122 | + | |
123 | + public static void addLinkedAccount(final AuthUser oldUser, | |
124 | + final AuthUser newUser) { | |
125 | + final UserCredential u = UserCredential.findByAuthUserIdentity(oldUser); | |
126 | + u.linkedAccounts.add(LinkedAccount.create(newUser)); | |
127 | + u.save(); | |
128 | + } | |
129 | + | |
130 | + public static UserCredential findByEmail(final String email) { | |
131 | + return getEmailUserFind(email).findUnique(); | |
132 | + } | |
133 | + | |
134 | + private static ExpressionList<UserCredential> getEmailUserFind(final String email) { | |
135 | + return find.where().eq("active", true).eq("email", email); | |
136 | + } | |
137 | + | |
138 | + public LinkedAccount getAccountByProvider(final String providerKey) { | |
139 | + return LinkedAccount.findByProviderKey(this, providerKey); | |
140 | + } | |
141 | + | |
142 | +} |
+++ app/service/YonaUserServicePlugin.java
... | ... | @@ -0,0 +1,52 @@ |
1 | +package service; | |
2 | + | |
3 | +import com.feth.play.module.pa.service.UserServicePlugin; | |
4 | +import com.feth.play.module.pa.user.AuthUser; | |
5 | +import com.feth.play.module.pa.user.AuthUserIdentity; | |
6 | +import models.UserCredential; | |
7 | +import play.Application; | |
8 | + | |
9 | +public class YonaUserServicePlugin extends UserServicePlugin { | |
10 | + | |
11 | + public YonaUserServicePlugin(final Application app) { | |
12 | + super(app); | |
13 | + } | |
14 | + | |
15 | + @Override | |
16 | + public Object save(final AuthUser authUser) { | |
17 | + final boolean isLinked = UserCredential.existsByAuthUserIdentity(authUser); | |
18 | + if (!isLinked) { | |
19 | + return UserCredential.create(authUser).id; | |
20 | + } else { | |
21 | + // we have this user already, so return null | |
22 | + return null; | |
23 | + } | |
24 | + } | |
25 | + | |
26 | + @Override | |
27 | + public Object getLocalIdentity(final AuthUserIdentity identity) { | |
28 | + // For production: Caching might be a good idea here... | |
29 | + // ...and dont forget to sync the cache when users get deactivated/deleted | |
30 | + final UserCredential u = UserCredential.findByAuthUserIdentity(identity); | |
31 | + if(u != null) { | |
32 | + return u.id; | |
33 | + } else { | |
34 | + return null; | |
35 | + } | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public AuthUser merge(final AuthUser newUser, final AuthUser oldUser) { | |
40 | + if (!oldUser.equals(newUser)) { | |
41 | + UserCredential.merge(oldUser, newUser); | |
42 | + } | |
43 | + return oldUser; | |
44 | + } | |
45 | + | |
46 | + @Override | |
47 | + public AuthUser link(final AuthUser oldUser, final AuthUser newUser) { | |
48 | + UserCredential.addLinkedAccount(oldUser, newUser); | |
49 | + return null; | |
50 | + } | |
51 | + | |
52 | +} |
--- app/utils/TemplateHelper.scala
+++ app/utils/TemplateHelper.scala
... | ... | @@ -1,7 +1,7 @@ |
1 | 1 |
package utils |
2 | 2 |
|
3 | 3 |
import org.apache.commons.lang3.StringUtils |
4 |
-import play.mvc.Call |
|
4 |
+import play.mvc.{Call, Http} |
|
5 | 5 |
import org.joda.time.DateTimeConstants |
6 | 6 |
import org.apache.commons.io.FilenameUtils |
7 | 7 |
import play.i18n.Messages |
... | ... | @@ -13,7 +13,6 @@ |
13 | 13 |
import playRepository.DiffLine |
14 | 14 |
import playRepository.DiffLineType |
15 | 15 |
import models.CodeRange.Side |
16 |
- |
|
17 | 16 |
import views.html.partial_diff_comment_on_line |
18 | 17 |
import views.html.partial_diff_line |
19 | 18 |
import views.html.git.partial_pull_request_event |
--- app/views/common/loginDialog.scala.html
+++ app/views/common/loginDialog.scala.html
... | ... | @@ -1,24 +1,22 @@ |
1 | 1 |
@** |
2 |
-* Yobi, Project Hosting SW |
|
2 |
+* Yona, 21st Century Project Hosting SW |
|
3 | 3 |
* |
4 |
-* Copyright 2014 NAVER Corp. |
|
5 |
-* http://yobi.io |
|
6 |
-* |
|
7 |
-* @author Jihan Kim |
|
8 |
-* |
|
9 |
-* Licensed under the Apache License, Version 2.0 (the "License"); |
|
10 |
-* you may not use this file except in compliance with the License. |
|
11 |
-* You may obtain a copy of the License at |
|
12 |
-* |
|
13 |
-* http://www.apache.org/licenses/LICENSE-2.0 |
|
14 |
-* |
|
15 |
-* Unless required by applicable law or agreed to in writing, software |
|
16 |
-* distributed under the License is distributed on an "AS IS" BASIS, |
|
17 |
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
18 |
-* See the License for the specific language governing permissions and |
|
19 |
-* limitations under the License. |
|
4 |
+* Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+* https://yona.io |
|
20 | 6 |
**@ |
21 | 7 |
@() |
8 |
+@import com.feth.play.module.pa.views.html._ |
|
9 |
+ |
|
10 |
+@providerWithLogo(provider:String) = @{ |
|
11 |
+ val googleLogo = routes.Assets.at("images/provider-logo/btn_google_light_normal_ios.svg") |
|
12 |
+ val githubLogo = routes.Assets.at("images/provider-logo/github.svg") |
|
13 |
+ provider match { |
|
14 |
+ case "github" => s"""<span class="auth-provider-logo"><span class="github"><svg aria-hidden="true" height="24" version="1.1" viewBox="0 0 16 16" width="20"><path |
|
15 |
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg></span> <span class="provider-name">Sign in with Github</span></span>""" |
|
16 |
+ case "google" => s"""<span class="auth-provider-logo"><img src="$googleLogo" alt="login with Google"> Sign in with Google</span>""" |
|
17 |
+ case _ => "" |
|
18 |
+ } |
|
19 |
+} |
|
22 | 20 |
|
23 | 21 |
<div id="loginDialog" class="modal hide loginDialog" tabindex="-1" role="dialog"> |
24 | 22 |
<div class="modal-body"> |
... | ... | @@ -51,6 +49,15 @@ |
51 | 49 |
<button type="submit" class="ybtn ybtn-primary fullsize">@Messages("button.login")</button> |
52 | 50 |
</div> |
53 | 51 |
|
52 |
+ <div class="btns-row nm"> |
|
53 |
+ @currentAuth() { auth => |
|
54 |
+ @if(auth == null) { |
|
55 |
+ @forProviders() { p => |
|
56 |
+ <a href="@p.getUrl()" class="ybtn oauth-login-btn">@Html(providerWithLogo(p.getKey()))</a> |
|
57 |
+ } |
|
58 |
+ } |
|
59 |
+ } |
|
60 |
+ </div> |
|
54 | 61 |
<div class="act-row right-txt mt20"> |
55 | 62 |
<div class="pull-left"> |
56 | 63 |
<input id="remember-meD" type="checkbox" name="rememberMe" class="checkbox" checked> |
--- app/views/index/partial_intro.scala.html
+++ app/views/index/partial_intro.scala.html
... | ... | @@ -1,23 +1,11 @@ |
1 | 1 |
@** |
2 |
-* Yobi, Project Hosting SW |
|
2 |
+* Yona, 21st Century Project Hosting SW |
|
3 | 3 |
* |
4 |
-* Copyright 2013 NAVER Corp. |
|
5 |
-* http://yobi.io |
|
6 |
-* |
|
7 |
-* @author Deokhong Kim |
|
8 |
-* |
|
9 |
-* Licensed under the Apache License, Version 2.0 (the "License"); |
|
10 |
-* you may not use this file except in compliance with the License. |
|
11 |
-* You may obtain a copy of the License at |
|
12 |
-* |
|
13 |
-* http://www.apache.org/licenses/LICENSE-2.0 |
|
14 |
-* |
|
15 |
-* Unless required by applicable law or agreed to in writing, software |
|
16 |
-* distributed under the License is distributed on an "AS IS" BASIS, |
|
17 |
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
18 |
-* See the License for the specific language governing permissions and |
|
19 |
-* limitations under the License. |
|
4 |
+* Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+* https://yona.io |
|
20 | 6 |
**@ |
7 |
+@import com.feth.play.module.pa.views.html._ |
|
8 |
+ |
|
21 | 9 |
<div class="siteintro-bg row"> |
22 | 10 |
<div class="siteintro"> |
23 | 11 |
<div class="siteintro-cover"> |
--- app/views/layout.scala.html
+++ app/views/layout.scala.html
... | ... | @@ -22,7 +22,8 @@ |
22 | 22 |
<meta name="twitter:title" content="@titleArray(0)" /> |
23 | 23 |
<meta name="twitter:url" content="@play.mvc.Http.Context.current().request().path()" /> |
24 | 24 |
<meta name="twitter:description" content="@{titleArray(titleArray.length-1)}" /> |
25 |
-<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.ico")"> |
|
25 |
+<link rel="shortcut icon" type="image/x-icon" href="@routes.Assets.at("images/favicon.ico")"> |
|
26 |
+<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> |
|
26 | 27 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("bootstrap/css/bootstrap.css")"> |
27 | 28 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("stylesheets/yobicon/style.css")"> |
28 | 29 |
<link rel="stylesheet" type="text/css" media="all" href="@routes.Assets.at("javascripts/lib/select2/select2.css")"/> |
+++ app/views/restricted.scala.html
... | ... | @@ -0,0 +1,31 @@ |
1 | +@(localUser: models.UserCredential = null) | |
2 | + | |
3 | +@import com.feth.play.module.pa.views.html._ | |
4 | + | |
5 | +@siteLayout(utils.Config.getSiteName, utils.MenuType.SITE_HOME) { | |
6 | + | |
7 | + <h1>Sshhh...don't tell anyone!</h1> | |
8 | + <p> | |
9 | + <iframe width="560" height="315" src="https://www.youtube.com/embed/9bZkp7q19f0" frameborder="0" allowfullscreen></iframe> | |
10 | + </p> | |
11 | + <p> | |
12 | + Your name is @localUser.name and your email address is @localUser.email | |
13 | + <i> | |
14 | + @if(!localUser.emailValidated) { | |
15 | + (unverified) | |
16 | + } else { | |
17 | + (verified) | |
18 | + }</i>! | |
19 | + | |
20 | + <br/> | |
21 | + @currentAuth() { auth => | |
22 | + Logged in with provider '@auth.getProvider()' and the user ID '@auth.getId()'<br/> | |
23 | + Your session expires | |
24 | + @if(auth.expires() == -1){ | |
25 | + never | |
26 | + } else { | |
27 | + at @auth.expires() (UNIX timestamp) | |
28 | + } | |
29 | + } | |
30 | + </p> | |
31 | +}(No newline at end of file) |
--- app/views/user/login.scala.html
+++ app/views/user/login.scala.html
... | ... | @@ -1,24 +1,25 @@ |
1 | 1 |
@** |
2 |
-* Yobi, Project Hosting SW |
|
2 |
+* Yona, 21st Century Project Hosting SW |
|
3 | 3 |
* |
4 |
-* Copyright 2012 NAVER Corp. |
|
5 |
-* http://yobi.io |
|
6 |
-* |
|
7 |
-* @author Hwi Ahn |
|
8 |
-* |
|
9 |
-* Licensed under the Apache License, Version 2.0 (the "License"); |
|
10 |
-* you may not use this file except in compliance with the License. |
|
11 |
-* You may obtain a copy of the License at |
|
12 |
-* |
|
13 |
-* http://www.apache.org/licenses/LICENSE-2.0 |
|
14 |
-* |
|
15 |
-* Unless required by applicable law or agreed to in writing, software |
|
16 |
-* distributed under the License is distributed on an "AS IS" BASIS, |
|
17 |
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
18 |
-* See the License for the specific language governing permissions and |
|
19 |
-* limitations under the License. |
|
4 |
+* Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+* https://yona.io |
|
20 | 6 |
**@ |
21 |
-@(message:String, authInfoForm: play.data.Form[AuthInfo], redirectUrl:String) |
|
7 |
+@import play.data.Form |
|
8 |
+@(message:String, authInfoForm: Form[AuthInfo], redirectUrl:String) |
|
9 |
+@import com.feth.play.module.pa.views.html._ |
|
10 |
+@import com.feth.play.module.pa.PlayAuthenticate._; |
|
11 |
+@import play.mvc.Http._; |
|
12 |
+ |
|
13 |
+@providerWithLogo(provider:String) = @{ |
|
14 |
+ val googleLogo = routes.Assets.at("images/provider-logo/btn_google_light_normal_ios.svg") |
|
15 |
+ val githubLogo = routes.Assets.at("images/provider-logo/github.svg") |
|
16 |
+ provider match { |
|
17 |
+ case "github" => s"""<span class="auth-provider-logo"><span class="github"><svg aria-hidden="true" height="24" version="1.1" viewBox="0 0 16 16" width="20"><path |
|
18 |
+ d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg></span> <span class="provider-name">Sign in with Github</span></span>""" |
|
19 |
+ case "google" => s"""<span class="auth-provider-logo"><img src="$googleLogo" alt="login with Google"> Sign in with Google</span>""" |
|
20 |
+ case _ => "" |
|
21 |
+ } |
|
22 |
+} |
|
22 | 23 |
|
23 | 24 |
@siteLayout(message, utils.MenuType.NONE) { |
24 | 25 |
<div class="page full"> |
... | ... | @@ -51,6 +52,16 @@ |
51 | 52 |
<button type="submit" class="ybtn ybtn-primary ybtn-large ybtn-fullsize">@Messages("button.login")</button> |
52 | 53 |
</div> |
53 | 54 |
|
55 |
+ <div class="btns-row nm"> |
|
56 |
+ @currentAuth() { auth => |
|
57 |
+ @if(auth == null) { |
|
58 |
+ @forProviders() { p => |
|
59 |
+ <a href="@p.getUrl()" class="ybtn oauth-login-btn">@Html(providerWithLogo(p.getKey()))</a> |
|
60 |
+ } |
|
61 |
+ } |
|
62 |
+ } |
|
63 |
+ </div> |
|
64 |
+ |
|
54 | 65 |
<div class="act-row mt5"> |
55 | 66 |
<div class="remember-me-wrap pull-left"> |
56 | 67 |
<input id="remember-me" type="checkbox" name="rememberMe" class="checkbox" checked> |
--- build.sbt
+++ build.sbt
... | ... | @@ -12,6 +12,9 @@ |
12 | 12 |
javaEbean, |
13 | 13 |
javaWs, |
14 | 14 |
cache, |
15 |
+ // PlayAuthenticat for social login |
|
16 |
+ // https://github.com/joscha/play-authenticate |
|
17 |
+ "com.feth" %% "play-authenticate" % "0.6.9", |
|
15 | 18 |
// OWASP Java HTML Sanitizer |
16 | 19 |
// https://www.owasp.org/index.php/OWASP_Java_HTML_Sanitizer_Project |
17 | 20 |
"com.googlecode.owasp-java-html-sanitizer" % "owasp-java-html-sanitizer" % "20160628.1", |
--- conf/application.conf.default
+++ conf/application.conf.default
... | ... | @@ -259,8 +259,17 @@ |
259 | 259 |
github.client.id = "TYPE YOUR GITHUB CILENT ID" |
260 | 260 |
github.client.secret = "TYPE YOUR GITHUB CILENT SECRET" |
261 | 261 |
|
262 |
- |
|
263 | 262 |
# Attachment Upload File Size Limit |
264 | 263 |
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
265 | 264 |
# 2,147,483,454 bytes = 2Gb |
266 | 265 |
application.maxFileSize = 2147483454 |
266 |
+ |
|
267 |
+# Social Login Support |
|
268 |
+# ~~~~~~~~~~~~~~~~~~~~ |
|
269 |
+# Social login settings for Yona |
|
270 |
+# Detail settings are described at conf/play-authenticate/mine.conf |
|
271 |
+ |
|
272 |
+# Prevent using Yona's own login system |
|
273 |
+application.use.social.login.only = true |
|
274 |
+ |
|
275 |
+include "social-login.conf" |
+++ conf/evolutions/default/12.sql
... | ... | @@ -0,0 +1,40 @@ |
1 | +# --- !Ups | |
2 | + | |
3 | +create table linked_account ( | |
4 | + id bigint auto_increment not null, | |
5 | + user_credential_id bigint, | |
6 | + provider_user_id varchar(255), | |
7 | + provider_key varchar(255), | |
8 | + constraint pk_linked_account primary key (id)) | |
9 | + row_format=compressed, key_block_size=8 | |
10 | +; | |
11 | + | |
12 | +create table user_credential ( | |
13 | + id bigint auto_increment not null, | |
14 | + user_id bigint, | |
15 | + login_id varchar(255), | |
16 | + email varchar(255), | |
17 | + name varchar(255), | |
18 | + active tinyint(1) default 0, | |
19 | + email_validated tinyint(1) default 0, | |
20 | + constraint pk_users primary key (id), | |
21 | + CONSTRAINT fk_user_credential_user FOREIGN KEY (user_id) REFERENCES n4user (id) on DELETE CASCADE) | |
22 | + row_format=compressed, key_block_size=8 | |
23 | +; | |
24 | + | |
25 | +create index ix_user_credential_user_id_1 on user_credential (user_id); | |
26 | + | |
27 | +alter table linked_account add constraint fk_linked_account_user_1 foreign key (user_credential_id) references user_credential (id) on delete CASCADE; | |
28 | + | |
29 | +create index ix_linked_account_user_credential_1 on linked_account (user_credential_id); | |
30 | + | |
31 | + | |
32 | +# --- !Downs | |
33 | + | |
34 | +SET FOREIGN_KEY_CHECKS=0; | |
35 | + | |
36 | +drop table linked_account; | |
37 | + | |
38 | +drop table user_credential; | |
39 | + | |
40 | +SET FOREIGN_KEY_CHECKS=1; |
--- conf/play.plugins
+++ conf/play.plugins
... | ... | @@ -1,1 +1,4 @@ |
1 |
+10005:service.YonaUserServicePlugin |
|
2 |
+10010:com.feth.play.module.pa.providers.oauth2.google.GoogleAuthProvider |
|
3 |
+10020:com.feth.play.module.pa.providers.oauth2.github.GithubAuthProvider |
|
1 | 4 |
15000:info.schleichardt.play2.mailplugin.MailPlugin |
--- conf/routes
+++ conf/routes
... | ... | @@ -10,6 +10,12 @@ |
10 | 10 |
# Home page |
11 | 11 |
GET / controllers.Application.index() |
12 | 12 |
|
13 |
+# Play!Authenticate |
|
14 |
+GET /logout controllers.Application.oAuthLogout |
|
15 |
+GET /authenticate/:provider controllers.Application.oAuth(provider: String) |
|
16 |
+GET /authenticate/:provider/denied controllers.Application.oAuthDenied(provider: String) |
|
17 |
+GET /restricted controllers.Restricted.index |
|
18 |
+ |
|
13 | 19 |
# Migration support page |
14 | 20 |
GET /migration controllers.MigrationApp.migration() |
15 | 21 |
GET /migration/projects controllers.MigrationApp.projects() |
+++ public/images/provider-logo/btn_google_light_normal_ios.svg
... | ... | @@ -0,0 +1,17 @@ |
1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
2 | +<svg width="30" height="30" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> | |
3 | + <!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch --> | |
4 | + <title>btn_google_light_normal_ios</title> | |
5 | + <desc>Created with Sketch.</desc> | |
6 | + <g id="Google-Button" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> | |
7 | + <g id="btn_google_light_normal" sketch:type="MSArtboardGroup" transform="translate(-1.000000, -1.000000)"> | |
8 | + <g id="logo_googleg_48dp" sketch:type="MSLayerGroup" transform="translate(7.000000, 7.000000)"> | |
9 | + <path d="M17.64,9.20454545 C17.64,8.56636364 17.5827273,7.95272727 17.4763636,7.36363636 L9,7.36363636 L9,10.845 L13.8436364,10.845 C13.635,11.97 13.0009091,12.9231818 12.0477273,13.5613636 L12.0477273,15.8195455 L14.9563636,15.8195455 C16.6581818,14.2527273 17.64,11.9454545 17.64,9.20454545 L17.64,9.20454545 Z" id="Shape" fill="#4285F4" sketch:type="MSShapeGroup"></path> | |
10 | + <path d="M9,18 C11.43,18 13.4672727,17.1940909 14.9563636,15.8195455 L12.0477273,13.5613636 C11.2418182,14.1013636 10.2109091,14.4204545 9,14.4204545 C6.65590909,14.4204545 4.67181818,12.8372727 3.96409091,10.71 L0.957272727,10.71 L0.957272727,13.0418182 C2.43818182,15.9831818 5.48181818,18 9,18 L9,18 Z" id="Shape" fill="#34A853" sketch:type="MSShapeGroup"></path> | |
11 | + <path d="M3.96409091,10.71 C3.78409091,10.17 3.68181818,9.59318182 3.68181818,9 C3.68181818,8.40681818 3.78409091,7.83 3.96409091,7.29 L3.96409091,4.95818182 L0.957272727,4.95818182 C0.347727273,6.17318182 0,7.54772727 0,9 C0,10.4522727 0.347727273,11.8268182 0.957272727,13.0418182 L3.96409091,10.71 L3.96409091,10.71 Z" id="Shape" fill="#FBBC05" sketch:type="MSShapeGroup"></path> | |
12 | + <path d="M9,3.57954545 C10.3213636,3.57954545 11.5077273,4.03363636 12.4404545,4.92545455 L15.0218182,2.34409091 C13.4631818,0.891818182 11.4259091,0 9,0 C5.48181818,0 2.43818182,2.01681818 0.957272727,4.95818182 L3.96409091,7.29 C4.67181818,5.16272727 6.65590909,3.57954545 9,3.57954545 L9,3.57954545 Z" id="Shape" fill="#EA4335" sketch:type="MSShapeGroup"></path> | |
13 | + <path d="M0,0 L18,0 L18,18 L0,18 L0,0 Z" id="Shape" sketch:type="MSShapeGroup"></path> | |
14 | + </g> | |
15 | + </g> | |
16 | + </g> | |
17 | +</svg>(No newline at end of file) |
+++ public/images/provider-logo/g-logo.png
Binary file is not shown |
+++ public/images/provider-logo/github.svg
... | ... | @@ -0,0 +1,6 @@ |
1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
2 | +<svg aria-hidden="true" height="44" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="46"> | |
3 | + <title>btn_github</title> | |
4 | + <desc>Modified by Suwon Chae</desc> | |
5 | + <path | |
6 | + d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg>(No newline at end of file) |
+++ public/images/yona-logo.png
Binary file is not shown |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?