jenny lin, developer


Previously the lead platform engineer here at and director of robot alchemy at Scribd, I now lead web and cloud at Play-i, where we are building robots to teach kids as young as five how to program. Welcome to my blog.

view:  full / summary

Integrating Discourse with Your Already-Existing User System

Posted on January 29, 2014 at 10:55 PM Comments comments (113)

At Play-i, we wanted to integrate a forum to our already existing site and user system. Discourse is a popular choice for forums. It is a nicely written open-source Ruby on Rails project. I found that deploying it on Heroku was a simple task using this fork of Discourse and these instructions. All I needed to do was to switch out the current login options to use the user system already on

After a lot of googling, while this was clearly something that many people had done before, it did NOT look like an easy task. Nor were there instructions on how to do it. This blog post is meant to be the set of instructions I was hoping to find when I started this task.

Step One: Turn your current project into an OAuth provider

Our existing user system is a Ruby on Rails project as well. I used Doorkeeper to turn it into an OAuth 2.0 provider. I followed the directions in the README, choosing to use ActiveRecord in the persisitence configuration step.

In my config/intiiailizers/doorkeeper.rb, I need to make sure that when users login from Discourse, they are sent back to the callback.

resource_owner_authenticator do

  User.find_by_id(session[:user_id]) || redirect_to(login_url(return_to: request.fullpath))


In my system, I already have it such that the login controller will store the return_to in the session and redirect there when done. If you don't already have this set-up, just add a session[:return_to] =  params[:return_to] to where your login url hits, and after the user is logged in, add a redirct_to session[:return_to] if session{:return_to]. This will make sure that once the user logs ins from your login page, it will complete the login into discourse.

Now your user system is ready to be used by Discourse for authentication.


Step Two: Provision your Discourse application to use your OAuth provider

In a rails console, I typed app = Doorkeeper::Application.create! :name => "Play-i Discourse", :redirect_uri => ""

From here, I could get the app.uid and app.secret to be used for the next step.


Step Three: Use OmniAuth to authenticate with your new OAuth provider


Luckily, Discourse comes with some authentication code already for OAuth2 providers. I created my own custom authenticator by extending the existing OAuth2 Authenticator:


Filename: lib/auth/playi_authenticator.rb


require 'auth/oauth2_authenticator'

class Auth::PlayiAuthenticator < ::Auth::OAuth2Authenticator

  def register_middleware(omniauth)

    omniauth.provider :playi, SiteSetting.playi_client_id, SiteSetting.playi_client_secret



  def after_authenticate(auth_token)

    result =

    oauth2_provider = auth_token[:provider]

    oauth2_uid = auth_token[:uid].to_s

    data = auth_token[:info] = email = data[:email] = name = data[:name]

    result.username = username = data[:username]


    oauth2_user_info = Oauth2UserInfo.where(uid: oauth2_uid, provider: oauth2_provider).first


    if !oauth2_user_info && @opts[:trusted] && user = User.find_by_email(email)

      oauth2_user_info = Oauth2UserInfo.create(uid: oauth2_uid,

                                               provider: oauth2_provider,

                                               name: name,

                                               email: email,

                                               user: user)



    result.user = oauth2_user_info.try(:user)

    result.email_valid = @opts[:trusted]


    result.extra_data = {

      uid: oauth2_uid,

      provider: oauth2_provider







An OmniAuth strategy is needed as well.

Filename: lib/omniauth/strategies/playi.rb


require 'omniauth-oauth2'


class OmniAuth::Strategies::Playi < OmniAuth::Strategies::OAuth2

  option :name, "playi" 

  option :client_options, site: ''

  uid { raw_info['id'] }


  info do


      :username => raw_info['username'],

      :name => raw_info['name'],

      :email => raw_info['email']




  extra do


      'raw_info' => raw_info




  def raw_info

    @raw_info ||= access_token.get('/oauth/me.json').parsed




Note: You may want to use a http://localhost:3000 as your site in your client_options to test your user system before deploying it.

Step Four: Tell Discourse about your new provider


Make sure that it knows about your new strategy:

In file app/controllers/users/omniauth_callbacks_controller.rb, add"playi") and @types ||=, :twitter, :google, :yahoo, :github, :persona, :cas, :playi)

In config/initializers/09-omniauth.rb add

require "omniauth/strategies/playi"

and in lib/auth.rb add

require_dependency 'auth/playi_authenticator'.


You also need to take the uid and secret you got from the last step and put them in config/site_settings.yml like so:


  client: true

  default: true

playi_client_id: '<app.uid>'

playi_client_secret: '<app.secret>'


If you want to turn off all other forms of logins, make sure the others are set to

default: false.

For me, I also wanted to turn off local logins and account creation as well.









With these setup, now you just need to configure Discourse to call it all.


in config/locales/client.en.yml



    title: "with Play-i"

    message: "Authenticating with your Play-i account (make sure pop up blockers are not enabled)"


In app/assets/javascripts/discourse/models/login_method.js add your new auth strategy name in the array. For me, that's "playi"


[ "google",










Great! Restart Discourse and when you hit the login button, your login method should show up. Make sure your other server is running, and click your login method. A window should pop-up with your login view which it found by calling your site url from your client options. Authenticate into your user system and it should redirect you back to Discourse. Now you will see a Discourse page to create a Discourse account with pre-filled values for name, email, and username, based on what you set in your strategy, grabbing from the me.json from your user system.

Step Five: Color Configuration on Login Method Button

While everything is working now, you may want to make a few more changes. Your login method is now an ugly grey color. If you want to configure this, add  a class in app/assets/stylesheets/common/components/buttons.css.scss like so:


&.playi {

    background: $playi;



and a color in app/assets/stylesheets/common/foundation/variables.scss with:

$playi: #00AFD7 !default;



Step Six: Prevent changes in usernames or emails

Now since I don't want users to be able to change their username or emails so they won't be out of sync with my system, I went through the config options to suppress the ability of users to be able to change them. These can be found in config/site_settings.yml by changing


username_change_period: 0

email_editable: false

enable names:

    default: false

The other problem is on that Discourse account creation screen, I wanted them to automatically use the values I received from authentication rather than prompting the user and allowing them to change the values at that point. I may have missed something, but I didn't see an easy Discourse configuration to do this, so I hacked it up a bit:


To get rid of the account creation screen, I went to app/assets/javascripts/discourse/controllers/login_controller.js and removed the lines related to that:


-    var createAccountController = this.get('controllers.createAccount');

-    createAccountController.setProperties({

-      accountEmail:,

-      accountUsername: options.username,

-      accountName:,

-      authOptions: Em.Object.create(options)

-    });

-    this.send('showCreateAccount');

+    window.location.reload();


Instead, I should do the user creation myself. So I did it in: app/controllers/users/omniauth_callbacks_controller.rb



-      session[:authentication] = @data.session_data

+      user = User.find_by_email(@data.session_data[:email])

+      user = User.create(name: @data.session_data[:name], email: @data.session_data[:email], username: @data.session_data[:username], active: true) if user.nil?

+      user_found(user)




This is the else for if the user is not already logged in. I just go ahead and create the user. Any user that can authenticate with my user system will be allowed in the forum.


Discourse requires their user have an additional email confirmation. I wanted to turn that off so in app/models/user.rb:


   def email_confirmed?




Finally, to make it feel like the same account, if they log out of discourse, they should also be logged out of their account on my user system. So in /app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js I changed this line:


     Discourse.User.logout().then(function() {

       // Reloading will refresh unbound properties


-      window.location.pathname = Discourse.getURL('/');

+      window.location.href = ""



There! Now Discourse should be integrated with your own user system and feel more like a seamless user experience.






What Start-Ups Like to See in Resumes from College Students and Entry-Level Candidates

Posted on September 30, 2011 at 8:55 PM Comments comments (11)

Last week, I represented Scribd at my alma mater Carnegie Mellon's job fair -- the TOC. While the quality of students that we met were incredible beyond what our recruiters had expected, the overall quality of the resume writing was honestly atrocious. I'm sure college seniors feel like they don't have a lot yet to put on a resume, but there are certainly ways to stand-out. This post is specifically about how to make your resume stand-out to a start-up.

Why a start-up? Start-ups are a lot of fun. The atmosphere is also closer to college life, which makes an easier transition. Working at a start-up is also a really great way to learn a ton since they are generally small with a lot of work and interesting problems. The start-ups I've worked at include Overture Technologies,, and now Scribd. The hours are extremely flexible (anything before 11am is considered "early"), the offices are filled with toys, snacks and drinks, and I really respected the intelligence, abilities, and passion of my co-workers. The last point goes to show why these companies look more for these qualities rather than for a list of skills. This post describes how to showcase these points on your resume.

List Personal Projects

Personal projects are a way to differentiate yourself from other candidates; they show that programming is part of who you are in life, not just your job. Personal projects can be as small and simple as a little game or a tutorial for a new language or framework you went through not because you needed to know it, but because you wanted to learn more about it. We weigh these things heavier than academic or work projects. I was flabbergasted to find that when I asked one student why his personal projects weren't on his resume, he answered, "They weren't real projects; I just did them for fun." That is exactly the reason they should be on your resume! Not everyone programs for fun. We want people for whom programming is fun, not just work.

List Only Academic Projects that Differentiate You

Most companies send alumni back to represent them at college job fairs. Being a Carnegie Mellon School of Computer Science alumna, I could easily recognize all the academic projects listed on various students' resumes. While "Diffusing a Binary Bomb" is really a great project and in fact the one I like to use as an example of why I thought our homework projects were really well-written, it is something that every CMU computer science student has to do. Meanwhile, projects in elective classes or ones where you must self-design your project helps a potential employer understand what sort of problems interest you. Specifying that it was an elective helps recruiters less familiar with various academic programs parse your resume.

Specify Links that Reference You and Your Work

Especially for college students, it is huge when we see someone who has a github account. Whether the github account shows original code, forked projects, or simply following other projects, it shows not only interest in the industry but also that you are already a part of the community. Links to projects are also useful; we can not only read about your work, we can see it first hand. Even links to twitter accounts or blogs are good as well. Particularly if you are applying to a social media start-up, it is good to see that you are a user of social media tools yourself and already have some domain knowledge.

List your Hobbies

We review so many resumes. Hobbies make us feel like you're more of a person than just a list of skills and qualifications. It also may help us determine whether you'd be a culture fit with the company.

Realize the Skills List is Not the Most Important Part of Your Resume

While more traditional companies may look for a checklist of skills, this is not the start-up mentality; start-ups look for smart, passionate people who can learn and pick-up anything. I remember being an entry-level candidate and listing skills that I had, but didn't really want to pursue at a company (like my sysadmin experience). I thought it was better to fill my resume with anything I could do rather than leave it off. As a result of course, I piqued the interest of several companies wanting me to do a role I was not interested in. I like to state it this way to candidates: if you are in the middle of figuring out a problem on Friday and you really have to leave work to go to a friend's birthday dinner, what sort of problem would make you more likely to want to continue figuring it out over the weekend rather than waiting until Monday to get back to it? I'm not saying don't like all the various skills you have, but know your passions and be sure to be forthright about what you are passionate about versus what you just "know how to do" or are simply "willing to do".

The start-up hiring mentality is just different from the traditional hiring mentality; what your parents advise you or what your college teaches may not be applicable here. Passion, intelligence, and love of the industry are what matter. If you are looking to apply to both start-ups and non-start-ups, I actually advise you to make two different resumes that emphasize things differently and make your own choice on what type of company you prefer after you get to see their offices and meet the employees.

If you are interested in working at Scribd (located in San Francisco) or (located in the DC area), please check out their job pages at and

Facebook and Girls in Tech Hackathon

Posted on September 13, 2010 at 12:00 AM Comments comments (1)

One of the advantages to moving out to the SF bay area office is to be able to go to great developer events and network. Over the weekend, I participated in the Facebook and Girls in Tech Hackathon. Having only moved to the area a few weeks ago, I had only heard of them but never participated before.  Prior to the event, all I knew was that this one was to go from noon on Saturday until noon on Sunday and participants had to from groups, come up with an idea, make it happen, and pitch it to the judges all in 24 hours. I grabbed my Macbook AIr, my Macbook Pro, some business cards, a blanket, and a sleeping bag and drove over to the Facebook Developer Garage.

Upon walking in, I was surprised that it wasn't female-only or even mostly female; the ratio was about 50-50. Now 50-50 is a much stronger female ratio than I'm used to in the field, but I had just assumed that this event was just for females since that was the way it was for all of the "females in science and technology" events I participated in back in middle and high school.

After signing in, we were directed to the courtyard for some lunch. There was plenty of time before the speakers were going to give their talks, so many people played around at the "Presence Photo" stations. Others mingled around the room doing networking. And some went around the room with the express purpose of trying to snipe people for their ideas before the group formation session officially began. People were very friendly, and I met a variety of entrepreneurs. Since we're an east coast based company, we do a pretty good job staying under the radar, so I wasn't surprised that people didn't immediately recognize our name. I was amused however, at which of our competitor's names they did recognize.

When everyone was done with lunch, the event started with Randi Zuckerberg hosting. She surprised journalist Kara Swisher by inviting her to moderate the panel. I was definitely impressed with the quality of the panel. I have looked up to Sandy Jen, co-founder of meebo, for a while now. Hearing her talk about the various ideas she tried and scrapped was inspiring. Herdlick Catherine from EA talked about needing to constantly defend games that sometimes others refuse to call "real games" even though they're starting to take over the market share (and I'm glad she does!). Though her company was the only one on the panel I had never heard of, I could directly relate with each one of the frustrations Jennifer Pahlke faces at Code for America; many of her stories talked about dealing with government red tape and being from DC and having worked at government contractors, I knew exactly what she meant. Also on the panel was Jocelyn Goldfein, who was hired three months ago as the new director of engineering at Facebook. She has an impressive resume though including VMWare and Netscape. She started out as a developer, but found herself transitioning to management after a few years.

The only thing that disappointed me about the panel was that none of them are coders anymore. Sandy Jen and Jocelyn Goldfein both started out as coders, but neither do anymore. I didn't get the sense that Herdlick Catherine or Jennifer Pahlke had ever touched code. I wouldn't replace any of the panel members they had, but I do wish that there was a female coder on the panel as well.

Following the panel speakers, Julie Tung talked about the Graph API, which was to be a requirement of the hackathon. Then they gave people who had ideas for the hackathon 2 minutes each to give a quick spiel about their idea and we broke off to form teams. I was looking for an idea that would fit the requirement of the Graph API, and sounded interesting to me as an actual product. Unfortunately, most of the projects sounded like people's start-up ideas that they wanted others to help code for them. From talking to other people in the crowd who had attended other area hackathons, some of these ideas were pitched at previous events. The project was pitched by Gonzo Maldonado stood out to me not for the idea, but because Gonzo was like me: someone happy with their current job and not looking to start a new company (Gonzo works at YouTube), but recently moved to the area and thought that a hackathon would both be fun and a great way to meet people. We agreed to work together and set-up shop on some couches. We created an initial git repository, and then started planning on a whiteboard. Slowly people came by and eventually 2 designers, a product manager, and another developer came to join us. I was surprised that as people approached me, there still seemed to be a bias where they assumed I am a UI/UX designer presumably because I'm female. I got many startled responses when I explained that I'm a back-end engineer; despite being at a technology event trying to get rid of gender stereotypes, I found I was still being stereotyped in a different way.

Having programmed almost exclusively in Java frameworks for the various companies I've worked for, I wanted to learn something new. Gonzo preferred Django and I was game to learn it. We coded straight through the night, taking only a one hour nap each. At nine in the morning, each group presented their products. Immediately before taking the podium, I was still checking in code to fix something I had just broken. At the end of all of the presentations, the panel explained who they were awarding prizes to and why. Finally, everyone was dismissed to lunch where mimosas were served in Facebook glasses which participants were invited to bring home.

In our end product, my group had code that found your geolocation, used that location to determine which craigslist was closest to you, ran a search on that craigslist for whatever you were looking for, and plotted those items on a map. We also had code that would send your Facebook Graph information to the seller, so the seller could have more information to verify that you are not scamming them. While the glue code for these working pieces was missing, I still thought it was an impressive amount of work. From the initial check-in prior to whiteboarding the plan to the final check-in that happened right before the presentation, we only had twelve hours. As expected, code quality degraded as the night went on, but I was still happy I got an introduction to a new framework and I can only hope Gonzo and the other members of team Couch Tomato (we chose to come up with a silly nonsensical name) also felt some sort of accomplishment. In addition to the sense of accomplishment from the coding itself, I found that it was successful as a networking event as well; I definitely felt like I became more of a part of the Silicon Valley start-up community from participating in this event. I hope in the future, webs can host this kind of event to create more of a start-up community in the DC area as well.