Giter Site home page Giter Site logo

auth0-guardian.js's Introduction

auth0-guardian-js Build Status

UI-less client for Guardian.

Installation

npm install auth0-guardian-js

CDN

Full version

https://cdn.auth0.com/js/guardian-js/1.3.3/guardian-js.js

Minified version

https://cdn.auth0.com/js/guardian-js/1.3.3/guardian-js.min.js

Basic Usage

Configuration

var auth0GuardianJS = require('auth0-guardian-js')({
	// For US tenants: https://{name}.guardian.auth0.com
 	// For AU tenants: https://{name}.guardian.au.auth0.com
 	// For EU tenants: https://{name}.guardian.eu.auth0.com
	serviceUrl: "https://{{ userData.tenant }}.guardian.auth0.com",
	requestToken: "{{ requestToken }}", // or ticket: "{{ ticket }}" - see below

	issuer: {
		// The issuer name to show in OTP Generator apps
		label: "{{ userData.tenantFriendlyName }}",
		name: "{{ userData.tenant }}",
	},

	// The account label to show in OTP Generator apps
	accountLabel: "{{ userData.friendlyUserId }}",

	// Optional, for debugging purpose only,
	// ID that allows to associate a group of requests
	// together as belonging to the same "transaction" (in a wide sense)
	globalTrackingId: "{{ globalTrackingId }}"
});

Use of requestToken or ticket depends on the authentication method. Ticket corresponds to a previously generated enrollment ticket.

Enrollment

To enroll a device is a process composed of the following steps:

  1. Start the transaction
  2. (optional) Check if the user is already enrolled. You cannot enroll twice.
  3. Send the information needed to enroll
  4. Confirm your enrollment
  5. Show the recovery code
  • Some steps can be omited depending on the method, we provide the same interface for all methods so you can write uniform code.
  • Some of the methods end up completing the authentication, whereas some others need an extra authentication step. You can know that by listening to the enrollment-complete event.
function enroll(transaction, method) {
	if (transaction.isEnrolled()) {
		console.log('You are already enrolled');
		return;
	}

	var enrollData = {};

	if (method === 'sms') {
		enrollData.phoneNumber = prompt('Phone number'); // Collect phone number
	}

	return transaction.enroll(method, enrollData, function (err, otpEnrollment) {
		if (err) {
			console.error(err);
			return;
		}

		var uri = otpEnrollment.getUri();
		if (uri) {
			showQR(uri);
		}

		var confirmData = {};
		if (method === 'otp' || method === 'sms') {
			confirmData.otpCode = prompt('Otp code'); // Collect verification otp
		}

		otpEnrollment.confirm(confirmData);
	});
}

auth0GuardianJS.start(function(err, transaction) {
	if (err) {
		console.error(err);
		return;
	}

	transaction.on('error', function(error) {
		console.error(error);
	});

	transaction.on('timeout', function() {
		console.log('Timeout');
	});

	transaction.on('enrollment-complete', function(payload) {
		if (payload.recoveryCode) {
			alert('Recovery code is ' + payload.recoveryCode);
		}

		if (payload.authRequired) {
			showAuthenticationFor(transaction, payload.enrollment);
			return;
		}
	});

	transaction.on('auth-response', function(payload) {
		if (payload.recoveryCode) {
			alert('The new recovery code is ' + payload.recoveryCode);
		}

		if (!payload.accepted) {
			alert('Authentication has been rejected');
			return;
		}

		auth0GuardianJS.formPostHelper('{{ postActionURL }}', { signature: payload.signature });
	});

	var availableEnrollmentMethods = transaction.getAvailableEnrollmentMethods();

	method = prompt('What method do you want to use, select one of '
		+ availableEnrollmentMethods.join(', '));

	enroll(transaction, method) // For sms
});

Authentication

To authenticate with a method you need to execute the following steps

  1. Start the transaction
  2. (optional) Check if the user is already enrolled. You need to be enrolled to authenticate.
  3. Request the auth (the push notification / sms). Request is a noop for OTP
  4. Verify the otp (.verify is a noop for push)

Some steps can be omitted depending on the method, we provide the same interface for all methods so you can write uniform code. After the factor is verified or the push accepted you will receive an auth-response event with the payload to send to the server, you can use the auth0GuardianJS.formPostHelper('{{ postActionURL }}', payload) to post back the message to the server.

You may also receive auth-rejected if the push notification was received.

function authenticate(method) {
	auth0GuardianJS.start(function (err, transaction) {
		if (err) {
			console.error(err);
			return;
		}

		if (!transaction.isEnrolled()) {
			console.log('You are not enrolled');
			return;
		}

		transaction.on('error', function(error) {
			console.error(error);
		});

		transaction.on('timeout', function() {
			console.log('Timeout');
		});

		transaction.on('auth-response', function(payload) {
			if (payload.recoveryCode) {
				alert('The new recovery code is ' + payload.recoveryCode);
			}

			if (!payload.accepted) {
				alert('Authentication has been rejected');
				return;
			}

			auth0GuardianJS.formPostHelper('{{ postActionURL }}', { signature: payload.signature });
		});

		var enrollment = transaction.getEnrollments()[0];

		if (enrollment.getAvailableAuthenticatorTypes().length === 0) {
			alert('Somethings went wrong, seems that there is no authenticators');
			return;
		}

		transaction.requestAuth(enrollment, { method: method } function(err, auth) {
			if (err) {
				console.error(err);
				return;
			}

			var data = {};
			if (method === 'sms' || method === 'otp') {
				data.otpCode = prompt('Otp code');
			} else if (method === 'recovery-code') {
				data.recoveryCode = prompt('Recovery code');
			}

			return auth.verify(data);
		});
	});
}

Recovery

DEPRECATED: This method has been deprecated and its usage is discouraged, use .requestAuth with method recovery-code (if available) instead.

Recovery works as authentication, but instead of passing an otpCode, you need to pass a recoveryCode to verify method

auth0GuardianJS.start(function(err, transaction) {
	if (err) {
		console.error(err);
		return;
	}

	transaction.on('error', function(error) {
		console.error(error);
	});

	transaction.on('timeout', function() {
		console.log('Timeout');
	});

	transaction.on('auth-response', function(payload) {
		if (payload.recoveryCode) {
			alert('The new recovery code is ' + payload.recoveryCode);
		}

		if (!payload.accepted) {
			alert('Authentication has been rejected');
			return;
		}

		auth0GuardianJS.formPostHelper('{{ postActionURL }}', { signature: payload.signature });
	});

	if (!transaction.isEnrolled()) {
		console.log('You are not enrolled');
		return;
	}

	var recoveryData = {};
	recoveryData.recoveryCode = prompt('Recovery code'); // Collect recovery code

	return transaction.recover(recoveryData);
});

Full API

First of all you need to instantiate the library

const auth0GuardianJS = require('auth0-guardian-js')({
	requestToken: //...,
	serviceUrl: //...,
	issuer: {
		name: //...,
		label: //...
	},
	accountLabel: //...
});

auth0GuardianJS.start(callback)

Starts a transaction on Guardian, callbacks with an error (if any) or a transaction ready to be used.

A transaction holds the current state of the operations being executed in Guardian and provides a bound point to listen to events and execute further operations.

auth0GuardianJS.start(function(err, transaction) {
  //...
});

auth0GuardianJS.resume(options, transactionState, callback)

This continues a transaction saved by transaction.serialize(). The options parameter provides the library user the opportunity to specify which kind of transport to use. Options include:

  • socket: a socket.io transport
  • polling: a polling transport.

If not set, the polling transport is used as default

This is a factory method, you SHOULD NOT instantiateauth0GuardianJS.

at some point in your code you stored the transaction:

auth0GuardianJS.start(function(err, transaction) {
  //...

  secureStorage.store('guardiantx', transaction.serialize());
});

Later, in another part of the code:

var serializedTransaction = secureStorage.get('guardiantx');

auth0GuardianJS.resume({ transport: 'polling' }, serializedTransaction, function(err, transaction) {
  //... continue using that transaction object.
});

Transaction

transaction.isEnrolled()

Returns true if user is already enrolled, false otherwise.

transaction.isEnrolled();

transaction.getAvailableEnrollmentMethods()

Returns an array of strings that represent the available enrollment methods, they are the methods that are currently available for you to enroll with.

The supported methods right now are:

  • otp: One time password manual input (e.g. Google Authenticator)
  • sms: SMS based otp, you will get an SMS as a second factor
  • push: Push notifications, you will get a push notification that you have to accept to confirm your identity.
transaction.getAvailableEnrollmentMethods();

transaction.getAvailableAuthenticationMethods()

DEPRECATED: Use .getAvailableAuthenticatorTypes() from the enrollment instead.

Returns an array of strings that represent the available authentication methods, they are the methods that are currently available for you to authenticate with.

The supported methods right now are:

  • otp: One time password manual input (e.g. Google Authenticator)
  • sms: SMS based otp, you will get an SMS as a second factor
  • push: Push notifications, you will get a push notification that you have to accept to confirm your identity.
transaction.getAvailableAuthenticationMethods();

transaction.getEnrollments()

Returns an array current user's enrollments objects, right now it will return single enrollment if user is enrolled, or no enrollment is user is not enrolled.

var enrollments = transaction.getEnrollments();

transaction.enroll(data, callback)

Starts an enrollment, this method is only valid when the user is not enrolled and there is at least an enrollment method available; otherwise it will return an error. It receives the data needed to enroll which is different depending on the method you want to use (see below) and callbacks with an error if it has been rejected or an enrollmentFlow if it has been accepted.

Enrollment data:

  • For sms:

    • phoneNumber: the phone number as a single international string
  • For push and otp: no data needed. You can pass null / undefined or an empty object.

var data = // Depends on the method
// data.phoneNumber for sms
// the other methods don't require extra data

transaction.enroll(data, function(err, enrollmentFlow) {

})

transaction.requestAuth(enrollment, [{ method: string }], callback)

Starts authentication, this method is only valid if user is enrolled and there is at least one authentication method available for this transaction and the enrollment (otherwise, will callback with an error). It receives the enrollment and optionally the method you want to enroll (otherwise the first one available on the enrollment will be used) and callbacks with an error or the authFlow. As result of this methods auth-response or error could be triggered.

transaction.requestAuth(enrollments[0], { method: enrollment.getAvailableMethods()[0] }, function(err, authFlow) {
	if (err) {
		// ...
	}

	var data = // otpCode for sms or otp; none for push
	authFlow.verify(data)
});

transaction.recover({ recoveryCode: string })

DEPRECATED: This method has been deprecated and its usage is discouraged, use .requestAuth with method recovery-code (if available) instead.

Authenticates using a recovery code, receives the recovery code as an string with just alpha numeric characters (no separators, etc.) as result of this method an error or auth-response error could be emitted; in case of auth-response it will include .recoveryCode as part of the payload, that recovery code is the new recovery code you must show to the user from him to save it.

transaction.recover({ recoveryCode: recoveryCode });

transaction.serialize()

The .serialize() method creates a plain javascript Object that should remain opaque to the library user. This must be stored by the user in a secure way.

This object is used in combination with auth0GuardianJS.resume

transaction.on(eventName, handler)

Listen for eventName and execute the handler if that event is received. The handler might include a payload with extra information about the event.

Events
enrollment-complete

Emitted when the enrollment has been completed; you could receive this event even when you have not started an enrollment from this transaction. In such case recoveryCode won't be available and it means that enrollment has been completed in a different transaction (for example in another tab), you should show authentication (authRequired will be true).

transaction.on('enrollment-complete', function ({ authRequired, recoveryCode, enrollment }) {
	// Enrollment confirmed

	// authRequired will be true if the enrollment methods require authentication

	// recoveryCode will be available only in case that the enrollment has been started from
	// the current transaction, you should show it for the user to write it down.

	// enrollment the shinny new enrollment, use it to authenticate if authRequired
	// is true
});
auth-response

Emitted when an authentication have been accepted or rejected. SMS and OTP enrollments also trigger this event since after those enrollments you are assumed to be authenticated; push enrollment doesn't trigger it and you should start the normal auth flow instead after enrollment. If there is an active enrollment in the current transaction, auth-response is guaranteed to be triggered after enrollment-complete.

transaction.on('auth-response', function({ accepted, recoveryCode, signature }) {
	// Auth response
	//
	// accepted: true if the authentication has been accepted, false otherwise
	//
	// signature: data to post to the verifier server for it to verify the transaction
	//  it is only available if the transaction has been accepted
	//
	// recoveryCode: only available after recovery, it is the new recovery code the
	// user should save securely.
});
timeout

Emitted when the transaction is no longer valid, you cannot enroll or authenticate. You should start a new transaction.

transaction.on('timeout', function () { // Transaction time out });

error

Emitted when there is an error on the transaction

transaction.on('error', function(error /* instanceOf GuardianError */) {
	// Errors that cannot be associated to a particular action, like socket.io errors or so
});

EnrollmentFlow

Let you confirm and execute operations on a not-yet-confirmed enrollment.

enrollmentFlow.getUri()

Returns the enrollment URI for methods that support uri-based enrollments (push and otp), this URI is usually presented as a QR code. It is a noop that returns null for for methods that don't support URI-based enrollment.

enrollmentFlow.getData()

Returns the enrollment data for methods that to transfer some data between devices in order to enroll (such as push and otp). This data could be used to generate the enrollment uri to show in a QR but since this is a common use case we provide enrollmentFlow.getUri() as a convenience method. The main use case for this data is when you want to use a different way to transfer the data instead of a QR code. For methods that don't need to transfer any data (such as sms) it is a noop that returns null.

The data includes the following fields:

{
  issuerLabel: // Issuer label
  otpSecret: // Base 64 encoded otp secret
  enrollmentTransactionId: // Transaction id to start enrollment exchange
  issuerName: // Issuer 'unique' name
  enrollmentId: // Id of current enrollment (pending confirmation)
  baseUrl: // Base url for mobile app
  algorithm: // Algorithm for otp generation
  digits: // Number of digits for otp generation
  counter: // Counter for otp generation
  period: // Duration of each otp code
}

enrollmentFlow.confirm(data)

Confirms the enrollment, an enrollment is not considered valid until it is confirmed the data needed to confirm the enrollment depends on the method (see below). As result of this method, error or enrollment-complete events can be fired.

Enrollment confirmation data:

  • For SMS:
    • otpCode: The otp code sent by sms to the phone number specified on the first step of enrollment.
  • For OTP:
    • otpCode: An otp code get from otp generator app (e.g. Google Authenticator)
  • For Push: No data needed, the enrollment is confirmed by the cell phone; you can omit this step but it is provided as a noop so you can write uniform code.

Auth Flow

authFlow.verify(data)

Let you verify the authentication by providing the verification data (see below). Push notfication does need this because the confirmation is done on the cell phone.

Auth verification data:

  • For SMS:
    • otpCode: The otp code sent by sms to the phone number specified on the first step of enrollment.
  • For OTP:
    • otpCode: An otp code get from otp generator app (e.g. Google Authenticator)
  • For Push: No data needed, the auth is verified by accepting the push notification on your cell phone; you can omit this step but it is provided as a noop so you can write uniform code.

authFlow.getMethod()

Returns the method associated with current auth flow; it might be sms, otp or push.

Enrollment

enrollment.getAvailableMethods()

DEPRECATED: Use .getAvaialbeAuthenticatorTypes instead. This method does not includes recovery authenticator which has become its own specific authenticator type.

Returns an array of strings that represent the authentication methods that can be used for this enrollment in the current transaction (the available methods might change from transaction to transaction). They depends on the methods supported by the enrollment and the methods currently allowed by the tenant.

enrollments[i].getAvailableMethods();

enrollment.getMethods()

Returns an array of strings that represent the authentication methods associated with this enrollments, they are the only methods that (if available for this transaction) can be used to authenticate the user based on the given enrollment.

enrollments[i].getMethods();

enrollment.getAvailableAuthenticatorTypes()

Returns an array of strings that represent the authenticator types (that used to be called methods) that can be used for this enrollment in the current transaction (the available types might change from transaction to transaction). They depend on the types supported by the enrollment and the methods currently allowed by the tenant.

enrollment.getName()

Returns the device name associated with the enrollment, this is a name you can show to the user; and it might be used to identify the device in an user-friendly way. This name is only available for push notification enrollments.

enrollment.getPhoneNumber()

Returns the phone number associated with the enrollment. This number will be masked and it is only meant to be used as a way for the user to identify the device that will receive the SMS.

Error object and error codes

Every error you can get from the api (callback error or error event) is an instance of Error and have the following format:

{
	message: string, // English message, description of the error
	errorCode: string // Unique identifier or the error
	statusCode: number // For http requests: http status code
}

Error codes

The error codes may (and should) be used to display informative messages and to distinguish between recoverable and unrecoverable errors. This is a list of the errors codes and its meaning

Error Code Description
invalid_token Invalid request or transaction token
insufficient_scope You don't have enought grants to perform the requested operation
invalid_bearer_format The bearer put in authentication header was not valid
enrollment_conflict There is another enrollment for the same user. You cannot enroll twice.
tenant_not_found The tenant associated cannot be found. Should not normally hapen at least that you delete the tenant
login_transaction_not_found The mfa auth transaction is not active or has already expired
error_sending_push_notification Push notification delivery failed
push_notification_wrong_credentials Push notification delivery failed because of wrong credentials
invalid_otp Provided otp code was not valid
invalid_recovery_code Provided recovery code was not valid
invalid_body Body validation failed. Bad request.
invalid_query_string Query string validation failed. Bad request.
enrollment_transaction_not_found The mfa enrollment transaction is not active or has expired
invalid_phone_number The provided phone number is invalid
error_sending_sms SMS Delivery error
feature_disabled The requested feature is currently globally not available (contact the provider)
feature_disabled_by_admin The requested feature is currently disabled by your admin
pn_endpoint_disabled We were unable to deliver the push notification after retrying many times. Try removing you account for the device and adding it again.
too_many_sms You have exeed the amount of SMSs assigned to your user
too_many_pn You have exeed the amount of push notifications assigned to your user
too_many_sms_per_tenant You have exeed the amount of SMSs assigned to your tenant
too_many_pn_per_tenant You have exeed the amount of push notifications assigned to your tenant
field_required A field is required to perform the operation (this errors has a field attribute with a code for the field: otpCode, recoveryCode)
method_not_found You have requested a method that is currently not supported (should not happen)
no_method_available There is currently no method to enroll (all of them are disabled)
enrollment_method_disabled The specified enrollment method is disabled, this error has also a .method field
auth_method_disabled The specified authentication method is disabled, this error has also a .method field
invalid_otp_format Otp format validation error
invalid_recovery_code_format Recovery code format validation error
transaction_expired The transaction has already expired
already_enrolled You are already enrolled, cannot enroll again
not_enrolled You not enrolled. Must enroll first
invalid_enrollment The enrollment provided to transaction#requestAuth method is not valid or is null/undefined

auth0-guardian.js's People

Contributors

connor-knabe avatar crew-security avatar dafortune avatar joseluisdiaz avatar mpast avatar santiagoaguiar avatar shadow-dahm avatar sre-57-opslevel[bot] avatar stevejarvis avatar tophermarie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

auth0-guardian.js's Issues

Add voice as an enrollment method and authentication method

Describe the problem you'd like to have solved

I would like the ability to get a voice based otp using my phone number only, similar to how SMS currently works.

Describe the ideal solution

  • transaction.getAvailableEnrollmentMethods() includes voice in the array if it is enabled
  • enrollment.getAvailableAuthenticatorTypes() returns voice in the array if it is enabled
  • all of the functions that work with the sms enrollment/authentication method would also work the same if the voice was selected (e.g. transaction.enroll(data, callback) accepts a phoneNumber when voice is the chosen method)

Alternatives and current work-arounds

Use SMS. This would be the closest thing to voice since only a phone number is required.

Additional context

This would be helpful for users with accessibility issues to verify using an otp.

Use ES5

Everyone coding in ES5 aren't using transpilers. We shouldn't force people to use it to be able to use this lib.

End-user's device clock out of sync causes token expired error

Description

When an end-user signs in and their device's clock is not sync'd with an accurate time server, they can experience an issue such as "credentials_expired". This occurs because a token is created using the time of their device as specified in this community post:
https://community.auth0.com/t/the-credentials-has-expired-error-on-mfa-page/89005

Is there a way that we can create the token using a "current time" value that is sent down from the server, rather than obtained from the end-user's device? This would eliminate these errors that users experience and remove the reliance on their device's clock to be accurately sync'd with a server.

Reproduction

Sign in as a user who is flagged to be enrolled in MFA. After successfully authenticating, they will receive the "credentials_expired" error, where they were expected to be taken to MFA enrollment.

Environment

Please provide the following:

  • Version of this library used:
  • Version of the platform or framework used, if applicable:
  • Other relevant versions (language, server software, OS, browser):
  • any browser
  • Other modules/plugins/libraries that might be involved:

Skip Promises

Capturing discussions on chat:

  • + We should support callbacks, more used in ES5.
  • + Callbacks are included without lib in ES5.
  • + Having ONE way to get a method's result is less complex.
  • - Repos using promise flows need to promisify.

Alternative 1: Supporting both callbacks and promises like amqplib

  • + Easy to use both with and without promises.
  • + Easy to implement.
  • - Increases complexity in implementation.

Alternative 2: Skipping both promises and callbacks. Since there are (or should be) events emitted in the end of every step, you could always bind the event instead.

  • + Even less complexity (since the events are there in all cases)
  • - Harder to use, having to unbind event handlers etc.

Safari Web Browser on iPad - Error 400

Hi,

I'm getting an error 400 when the lib is executed using Safari on iPad.
image

With other browsers, it works perfectly! Even if I'm using Safari on macOS, it works. Only happens on iPad devices.

Tested in different iPads.

Undefined Error?!

GuardianError

errorCode: undefined

message: undefined

stack: "crossDomainError↵onreadystatechange↵wrapFn↵onInvokeTask↵runTask↵invokeTask↵invokeTask↵globalZoneAwareCallback"

statusCode: undefined

Whats wrong here?
Can'f find the bug.
Safari says it could not load certificate for https://guardian.**.auth0.com/api/start-flow

Grammar issue on the error message

Description

We noticed a grammar issue on the Auth0 widget. should be either THE CREDENTIAL HAS EXPIRED or THE CREDENTIALS HAVE EXPIRED ?

image

The source code is located at https://github.com/auth0/auth0-guardian.js/blob/master/lib/errors/credentials_expired_error.js

Actually, we are using https://cdn.auth0.com/js/mfa-widget/mfa-widget-1.8.min.js
I am wondering if you correct the grammar on auth0-guardian.js, how could we pick it up? what's the relationship between auth0-guradian.js and mfa-widget? I rarely see the docs related to mfa-widget on auth0 official site.

Reproduction

N/A

Environment

  • Version of this library used:
  • Version of the platform or framework used, if applicable:
  • Other relevant versions (language, server software, OS, browser):
  • Other modules/plugins/libraries that might be involved:

Add recoverability to errors list

The list of errors should state if the error is recoverable or not.

(NB: Make sure the list is complete, e.g. I think self enrollment errors are not listed here.)

Ability to remove event listeners to avoid side effects

Please do not report security vulnerabilities here. The Responsible Disclosure Program details the procedure for disclosing security issues.

Thank you in advance for helping us to improve this library! Your attention to detail here is greatly appreciated and will help us respond as quickly as possible. For general support or usage questions, use the Auth0 Community or Auth0 Support. Finally, to avoid duplicates, please search existing Issues before submitting one here.

By submitting an Issue to this repository, you agree to the terms within the Auth0 Code of Conduct.

Describe the problem you'd like to have solved

I use react to build MFA related pages and have different components handle different page (e.g. Push notification, SMS, backup Code). Thus, I'd like to handle a particular event (transaction.on('error') transaction.on('auth-response')) differently on different pages.

However, when user switch between pages, the previous event listeners are still there, which interrupt on the logic in the new page.

Describe the ideal solution

I'd like to be able to remove the listeners before the page unload, so that there is no side effects

Alternatives and current work-arounds

A clear and concise description of any alternatives you've considered or any work-arounds that are currently in place.

Additional context

Add any other context or screenshots about the feature request here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.