Skip to content

Commit

Permalink
add RemoteCode component. quickstarts collection. rails quickstart
Browse files Browse the repository at this point in the history
  • Loading branch information
bhalsey committed Jun 5, 2023
1 parent 972ad63 commit fe44066
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 0 deletions.
23 changes: 23 additions & 0 deletions astro/.astro/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ declare module 'astro:content' {
collection: "articles",
data: InferEntrySchema<"articles">
} & { render(): Render[".md"] },
"authentication/how-sso-works.mdx": {
id: "authentication/how-sso-works.mdx",
slug: "authentication/how-sso-works",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
} & { render(): Render[".mdx"] },
"authentication/index.mdx": {
id: "authentication/index.mdx",
slug: "authentication",
Expand Down Expand Up @@ -757,6 +764,22 @@ declare module 'astro:content' {
collection: "dev-tools",
data: InferEntrySchema<"dev-tools">
} & { render(): Render[".mdx"] },
},
"quickstarts": {
"index.mdx": {
id: "index.mdx",
slug: "index",
body: string,
collection: "quickstarts",
data: InferEntrySchema<"quickstarts">
} & { render(): Render[".mdx"] },
"quickstart-ruby-rails-web.mdx": {
id: "quickstart-ruby-rails-web.mdx",
slug: "quickstart-ruby-rails-web",
body: string,
collection: "quickstarts",
data: InferEntrySchema<"quickstarts">
} & { render(): Render[".mdx"] },
},

};
Expand Down
66 changes: 66 additions & 0 deletions astro/src/components/RemoteCode.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
/**
* Remote code fetcher and syntax highlighter via the Astro Code component.
*/
export interface Props {
/**
* Url to fetch code from. Also allows selecting specific line(s) of code with
* the Github line syntax.
* E.g., https://raw.githubusercontent.com/FusionAuth/fusionauth-example-client-libraries/main/ruby/Gemfile#L3
*/
url: string;
/**
* The language of your code.
* Supports all languages listed here: https://github.com/shikijs/shiki/blob/main/docs/languages.md#all-languages
*
* @default "plaintext"
*/
lang?: string;
/**
* Optional tags marking beginning and end of content to include
* Loosely based on https://docs.asciidoctor.org/asciidoc/latest/directives/include-tagged-regions/
* Note: Currently only supports one set of tags.
*/
tags?: string;
}
const { url, lang = 'plaintext', tags} = Astro.props as Props;
import { Code } from 'astro/components';
const remoteResponse = await fetch(url);
const remoteCode = await remoteResponse.text();
let remoteSelectedCode;
if (tags) {
remoteSelectedCode = selectTagged(remoteCode, tags);
} else {
const matchLines = url.match(/.*#L(\d+)(-L(\d+))?$/);
if (matchLines) {
remoteSelectedCode = selectLines(remoteCode, matchLines[1], matchLines[3]);
} else {
remoteSelectedCode = remoteCode.trim();
}
}
function selectTagged(content: string, tags: string): string {
const lines = remoteCode.split("\n");
const startLine = lines.findIndex((line) => line.includes(`tag::${tags}`));
const endLine = lines.findIndex((line) => line.includes(`end::${tags}`));
return lines.slice(startLine + 1, endLine).join("\n");
}
function selectLines(content: string, lineNum: string, lineEnd: string): string {
const lines = content.split("\n");
if (lineNum && lineEnd) {
return lines.slice(lineNum-1, lineEnd-1).join("\n");
} else if (lineNum) {
return lines[lineNum - 1];
} else {
return content;
}
}
---
<Code code={remoteSelectedCode} lang={lang}/>
13 changes: 13 additions & 0 deletions astro/src/components/quickstarts/Index.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
import ClickableCard from "../ClickableCard.astro";
import { getCollection } from "astro:content";
const { section } = Astro.props;
const tools = await getCollection("quickstarts");
---

<div class="gap-6 grid grid-cols-1 not-prose lg:grid-cols-2" xmlns="http://www.w3.org/1999/xhtml">
{tools.map(tool =>
<ClickableCard href={`/quickstarts/${tool.slug}`} title={tool.data.title} description={tool.data.description} icon={tool.data.icon ? tool.data.icon : null}/>
)}
</div>
15 changes: 15 additions & 0 deletions astro/src/content/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,22 @@ const devToolsCollection = defineCollection({
}),
});

const quickstartsCollection = defineCollection({
schema: z.object({
author: z.string().optional(),
description: z.string(),
disableTOC: z.boolean().default(false),
excludeFromNav: z.boolean().default(false),
icon: z.string().optional(),
section: z.string(),
title: z.string(),
sortTitle: z.string().optional(),
featured: z.boolean().default(false),
}),
});

export const collections = {
'articles': articlesCollection,
'dev-tools': devToolsCollection,
'quickstarts': quickstartsCollection,
};
9 changes: 9 additions & 0 deletions astro/src/content/quickstarts/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Quickstarts
description: Enjoy these web based developer tools that our team has put together.
disableTOC: true
section: web
---
import Index from "../../components/quickstarts/Index.astro";

<Index/>
195 changes: 195 additions & 0 deletions astro/src/content/quickstarts/quickstart-ruby-rails-web.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
---
title: Quickstart Ruby on Rails and FusionAuth
description: Quickstart integration of Ruby on Rails web application with FusionAuth
navcategory: getting-started
prerequisites: Ruby, bundler and Rails
section: web
technology: Ruby on Rails
language: Ruby
---
import RemoteCode from '/src/components/RemoteCode.astro';

In this tutorial, you are going to learn how to integrate a Ruby on Rails application with FusionAuth.
The docker compose file and source code for a complete application are available at
https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web

## Prerequisites

- [Ruby](https://www.ruby-lang.org/en/documentation/installation/): This will be needed for pulling down the various dependencies.
- [Rails](https://guides.rubyonrails.org/getting_started.html): This will be used in order to run the Rails server.
- [Docker](https://www.docker.com): The quickest way to stand up FusionAuth.
- (Alternatively, you can [Install FusionAuth Manually](https://fusionauth.io/docs/v1/tech/installation-guide/)).

This app has been tested with Ruby 3.2.2 and Rails 7.0.4.3

## FusionAuth Installation via Docker

The root of this project directory (next to this README) are two files [a Docker compose file](./docker-compose.yml) and an [environment variables configuration file](./.env). Assuming you have Docker installed on your machine, you can stand up FusionAuth up on your machine with:

```
git clone https://github.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web.git
cd fusionauth-quickstart-ruby-on-rails-web
docker-compose up -d
```

The FusionAuth configuration files also make use of a unique feature of FusionAuth, called [Kickstart](https://fusionauth.io/docs/v1/tech/installation-guide/kickstart): when FusionAuth comes up for the first time, it will look at the [Kickstart file](./kickstart/kickstart.json) and mimic API calls to configure FusionAuth for use when it is first run.

> **NOTE**: If you ever want to reset the FusionAuth system, delete the volumes created by docker-compose by executing `docker-compose down -v`.
FusionAuth will be initially configured with these settings:

* Your client Id is: `e9fdb985-9173-4e01-9d73-ac2d60d1dc8e`
* Your client secret is: `super-secret-secret-that-should-be-regenerated-for-production`
* Your example username is `richard@example.com` and your password is `password`.
* Your admin username is `admin@example.com` and your password is `password`.
* Your fusionAuthBaseUrl is 'http://localhost:9011/'

You can log into the [FusionAuth admin UI](http://localhost:9011/admin) and look around if you want, but with Docker/Kickstart you don't need to.

## Create your Ruby on Rails Application

Now you are going to create a Ruby on Rails application. While this section builds a
simple Ruby on Rails application, you can use the same configuration to
integrate your existing Ruby on Rails application with FusionAuth.

```bash
rails new myapp && cd myapp
```

### Configure Omniauth

Install the omniauth gem and other supporting gems. Add the following three lines to your `Gemfile`.

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/Gemfile"
lang="ruby"
tags="gemfile"/>

Then, install them.

```bash
bundle install
```

Next, update your `config/environments/development.rb` file with FusionAuth OpenID Connect (OIDC) environment specific configuration information.

> **NOTE**: You'll have to add similar configuration to the relevant environment files when deploying to prod or other environments.
<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/config/environments/development.rb"
lang="ruby"
tags="oidcConfig"/>

Configure Omniauth by creating `config/initializers/omniauth.rb` with the following

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/config/initializers/omniauth.rb"
lang="ruby" />

This pulls from the environment file and configures omniauth to communicate with FusionAuth.

### Add Controllers

Next, you can create some controllers with the following shell commands:

```shell
rails generate controller auth
rails generate controller home
rails generate controller make_change
```

These controllers have the following purposes:

* `auth` is for omniauth integration
* `home` is an unprotected home page with a login button
* `make_change` is a protected page for our example bank application

First, let's update the `config/routes.rb` file. Here's what that should look like:

<RemoteCode url="
https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/config/routes.rb"
lang="ruby" />

Some simple routes corresponding to the controllers:

* `make_change` is the protected bank page
* `home` is the home page, which is available to unauthenticated users. This is also the default page.
* `logout` is tied to the auth controller's logout method.
* `auth/:provider/callback` is the omniauth callback method, which completes the OpenID Connect (OIDC) grant.

Now, update the auth controller at `app/controllers/auth_controller.rb` to look like this, which fulfills some of the routes above.

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/controllers/auth_controller.rb"
lang="ruby" />

This lets us have a nice `logout` method and also handle the callback from omniauth. The latter sets a `session` attribute with user data, which can be used by views later.

Now, update the application controller at `app/controllers/application_controller.rb`.

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/controllers/application_controller.rb"
lang="ruby" />

- `authenticate_user!` enforces authentication for all routes in your application by checking for the session attribute set by the auth controller after a successful login.
- `redirect_non_localhost!` ensures users access the web app via `localhost` instead of a url like http:127.0.0.1:3000/ . FusionAuth's origin and redirect URL configurations in this example expect `localhost`. In production, similar logic should be updated with your domain name.

Now, let's build out the home page. Update the home controller at `app/controllers/home_controller.rb` to look like this:

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/controllers/home_controller.rb"
lang="ruby" />

You're skipping authentication for this route. This is so that a user has someplace to go if they are unauthenticated.

### Add Views

The view is also welcoming, but prompts them to login. Otherwise, it shows a mock account balance. Replace `app/views/home/index.html.erb` with this:

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/views/home/index.html.erb"
lang="ruby" />

Finally, update the layout so the user has login or logout buttons on every page. Add the below code to `app/views/layouts/application.html.erb` just after the `<body>` tag.

<RemoteCode url="https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/views/layouts/application.html.erb"
lang="ruby"
tags="applicationLayout"/>

### Add Styling

Now, add some image assets and styling to make this look like a real application with the following shell commands:

```shell
wget -o app/assets/images/example_bank_logo.svg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/example_bank_logo.svg
wget -o app/assets/images/fusion_auth_logo.svg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/fusion_auth_logo.svg
wget -o app/assets/images/money.jpg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/images/money.jpg
wget -o app/assets/stylesheets/changebank.css https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-ruby-on-rails-web/main/complete-app/app/assets/stylesheets/changebank.css
```

Once you’ve created these files, you can test the application.

## Run the App

Start up the Rails application using this command:

```shell
OP_SECRET_KEY="super-secret-secret-that-should-be-regenerated-for-production" bundle exec rails s
```

`OP_SECRET_KEY` is the client secret, which was defined by the [FusionAuth Installation via Docker](#fusionauth-installation-via-docker) step. You don't want to commit secrets like this to version control, so use an environment variable.

You can now open up an incognito window and visit the Rails app at http://localhost:3000 .
Log in with the user account you created when setting up FusionAuth, and you’ll see the email of the user next to a logout button.

## Troubleshooting

* I get `This site can’t be reached localhost refused to connect.` when I click the Login button

Ensure FusionAuth is running in the Docker container. You should be able to login as the admin user, `admin@example.com` with the password of `password` at http://localhost:9011/admin

* I get an error page when I click on the Login button with message of `"error_reason" : "invalid_client_id"`

Ensure the value for `config.x.fusionauth.client_id` in the file `config/environments/development.rb` matches client id configured in FusionAuth for the Example App application at http://localhost:9011/admin/application/

* I'm getting an error from Rails after logging in

```
Rack::OAuth2::Client::Error
invalid_client :: Invalid client authentication credentials.
```

This indicates that Omniauth is unable to call FusionAuth to validate the returned token. It is likely caused my not supplying the correct *client secret*. Ensure the `OP_SECRET_KEY` used to start rails matches the FusionAuth ExampleApp client secret. http://localhost:9011/admin/application/
24 changes: 24 additions & 0 deletions astro/src/layouts/Quickstart.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
import { getCollection } from "astro:content";
import Default from "./Default.astro";
const quickstarts = await getCollection("quickstarts");
const sections = [];
for (const quickstart of quickstarts) {
if (quickstart.id.indexOf("index.md") !== -1) {
continue;
}
sections.push(
{
"title": quickstart.data.title,
"path": `/quickstarts/${quickstart.slug}`,
"icon": quickstart.data.icon,
"color": quickstart.data.color
}
)
}
---
<Default sections={sections} sideMenu={[]} {...Astro.props}>
<slot/>
</Default>
17 changes: 17 additions & 0 deletions astro/src/pages/quickstarts/[...slug].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Quickstart.astro";
export async function getStaticPaths() {
const entry = await getCollection("quickstarts");
return entry.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content, headings } = await entry.render();
---
<Layout frontmatter={entry.data} headings={headings}>
<Content/>
</Layout>
Loading

0 comments on commit fe44066

Please sign in to comment.